Вопрос по Курсу: "Параллельное программирование с использованием MS VisualStudia 2010". При компиляции Самостоятельного задания (одновременная отрисовка прямоугольников, эллипсов и выдача в текст-бокс случайного числа) среда предупреждает: suspend - устаревшая команда; примените monitor, mutex и т.п. Создаётся впечатление, что Задание создано в более поздней среде, чем VS 2010. |
Синхронизация потоков
Методы класса 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.
увеличить изображение
Рис. 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.
увеличить изображение
Рис. 5.4. Результат выполнения программы без использования методов Wait() и Pulse()
Как видно из результатов выполнения программы, методы tick() и tock() больше не синхронизированы.