Томский политехнический университет
Опубликован: 23.01.2013 | Доступ: свободный | Студентов: 1158 / 192 | Длительность: 12:09:00
Лекция 4:

Синхронизация потоков

< Лекция 3 || Лекция 4: 12345 || Лекция 5 >

Методы класса Monitor: Wait, Pulse и PulseAll

Методы Wait(), Pulse() и PulseAll() определены в классе Monitor и могут вызываться только из заблокированного фрагмента блока (lock - оператор). Когда выполнение потока временно блокируется, вызывается метод Wait(), т.е. он переходит в режим ожидания и снимает блокировку с объекта, позволяя другому потоку использовать этот объект. Позже, когда другой поток входит в аналогичное состояние блокирования и вызывает метод Pulse() или PulseAll(), "спящий" поток "просыпается". Обращение к методу Pulse() возобновляет выполнение потока, стоящего первым в очереди потоков, пребывающих в режиме ожидания. Обращение к методу PulseAll() сообщает о снятии блокировки всем ожидающим потокам.

Два наиболее используемых формата использования метода Wait():

public static bool Wait(object waitOb)
public static bool Wait(object waitOb, int миллисекунд_простоя)

Первый формат означает ожидание до уведомления. Второй - ожидает до уведомления или до истечения периода времени, заданного в миллисекундах. В обоих случаях параметр waitOb задает объект, к которому ожидается доступ. Форматы использования методов Pulse() и PulseAll() приведены ниже:

public static void Pulse(object waitOb)  
public static void PulseAll(object waitOb)

где параметр waitOb - означает объект, освобождаемый от блокировки.

Если метод Wait(), Pulse() или PulseAll() вызывается из кода, который находится вне lock блока, генерируется исключение типа SynchronizationLockException.

В качестве примера работы методов Wait() и Pulse(), создадим программу, которая имитирует работу часов посредством отображения на экране слов "тик" и "так". Для этого создадим класс TickTock, который содержит два метода: tick() - отображает слово "тик" и tock() - отображает слово "так":

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace TickTack
{
    class TickTock
    {
        
        public void tick(bool running)
        {
            lock (this)
            {
                if (!running)  { // Остановка часов.
                    Monitor.Pulse(this);  // Уведомление любых 
                                          // ожидающих потоков. 
                    return;
                }
                Console.Write("тик ");
                Monitor.Pulse(this);  // Разрешает выполнение
                                      // метода tock().
                Monitor.Wait(this);   // Ожидаем завершения
                                      // метода tock(). 
            }
        }
        public void tock(bool running)
        {
            lock (this)
            {
                if (!running)            { // Остановка часов.
                    Monitor.Pulse(this);  // Уведомление любых
                                          // ожидающих потоков. 
                    return;
                }
                Console.WriteLine("так");
                Monitor.Pulse(this);  // Разрешает выполнение
                                      // метода tick().
                Monitor.Wait(this);   // Ожидаем завершения
                                      // метода tick(). 
            }
        }
    }
    class MyThread
    {
        public Thread thrd;
        TickTock ttOb;
              // Создаем новый поток.
        public MyThread(string name, TickTock tt)
        {
            thrd = new Thread(new ThreadStart(this.run));
            ttOb = tt;
            thrd.Name = name;
            thrd.Start();
        }
        // Начинаем выполнение нового потока. 
        void run()
        {
            if (thrd.Name == "тик")
            {
                for (int i = 0; i < 5; i++) ttOb.tick(true);
                ttOb.tick(false);
            }
            else
            {
                for (int i = 0; i < 5; i++) ttOb.tock(true);
                ttOb.tock(false);
            }
        }
    }
   
    class Program
 {
      static void Main(string[] args)
        {
            TickTock tt = new TickTock();
            MyThread mt1 = new MyThread("тик", tt);
            MyThread mt2 = new MyThread("так", tt);
            mt1.thrd.Join();
            mt2.thrd.Join();
            Console.WriteLine("Часы остановлены");
            Console.ReadLine();
        }
  }
}

При выполнении программа сгенерирует результаты, представленные на Рис. 5.3.

 Результат выполнения программы с использованием методов Wait() и Pulse()

увеличить изображение
Рис. 5.3. Результат выполнения программы с использованием методов Wait() и Pulse()

В методе Main() создается объект класса TickTock - tt, который используется для запуска двух потоков на выполнение. Если в методе Run() из класса MyThread обнаруживается имя потока mt1, соответствующее ходу часов "тик", то вызывается метод tick(). А если это имя потока mt2, соответствующее ходу часов "так", то вызывается метод tock().

Каждый из их методов вызывается пять раз подряд с передачей логического значения true в качестве аргумента. Часы идут до тех пор, пока этим методам передается логическое значение true, и останавливаются, как только передается логическое значение false:

public void tick(bool running)
        {
            lock (this)
            {
                if (!running)  { // Остановка часов.
                    Monitor.Pulse(this);  // Уведомление любых 
                                          // ожидающих потоков. 
                    return;
                }
                Console.Write("тик ");
                Monitor.Pulse(this);  // Разрешает выполнение
                                      // метода tock().
                Monitor.Wait(this);   // Ожидаем завершения
                                      // метода tock(). 
            }
        }

Методы Wait() и Pulse() могут использоваться только в синхронизированных блоках кода. Вначале метода tick() проверяется значение текущего параметра, которое служит явным признаком остановки часов. Если это логическое значение false, то часы остановлены. В этом случае вызывается метод Pulse(), разрешающий выполнение любого потока, ожидающего своей очереди.

Если же часы идут при выполнении метода tick(), то на экран выводится слово "тик" с пробелом, затем вызывается метод Pulse(), а после него - метод Wait(). При вызове метода Pulse() разрешается выполнение потока для того же самого объекта, а при вызове метода Wait() выполнение метода tock() приостанавливается до тех пор, пока метод Pulse() не будет вызван из другого потока. Теперь, уберем методы Wait() и Pulse() из созданной ранее программы:

class TickTock
    {
        public void tick(bool running)
        {
            lock (this)
            {
                if (!running)
                { // Остановка часов.
                    return;
                }
                Console.Write("тик ");
            }
        }
        public void tock(bool running)
        {
            lock (this)
            {
                if (!running)
                { // Остановка часов.
                    return;
                }
                Console.WriteLine("так");
            }
        } 
    }

При выполнении программа сгенерирует результаты, представленные на Рис. 5.4.

 Результат выполнения программы без использования методов Wait() и Pulse()

увеличить изображение
Рис. 5.4. Результат выполнения программы без использования методов Wait() и Pulse()

Как видно из результатов выполнения программы, методы tick() и tock() больше не синхронизированы.

< Лекция 3 || Лекция 4: 12345 || Лекция 5 >
Владимир Каширин
Владимир Каширин

Вопрос по Курсу: "Параллельное программирование с использованием MS VisualStudia 2010".

При компиляции Самостоятельного задания (одновременная отрисовка прямоугольников, эллипсов и выдача в текст-бокс случайного числа) среда предупреждает: suspend - устаревшая команда; примените monitor, mutex и т.п.

Создаётся впечатление, что Задание создано в более поздней среде, чем VS 2010.