Методы класса 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() больше не синхронизированы.
Класс Mutex
Когда двум или более потокам одновременно требуется доступ к общему ресурсу, системе необходим механизм синхронизации, чтобы обеспечить использование ресурса только одним потоком одновременно. Класс Mutex, определенный в пространстве имен System. Threading - это примитив, который предоставляет эксклюзивный доступ к общему ресурсу только одному потоку синхронизации. Если поток получает семафор, второй поток, желающий получить этот семафор, приостанавливается до тех пор, пока первый поток не освободит семафор. Термин Mutex происходит от фразы mutually exclusive (взаимно исключающий), и поскольку только один поток может получить блокировку монитора для данного объекта в любой момент времени, только один поток в любой момент времени может получить данный Mutex. Класс Mutex очень похож на класс Monitor тем, что тоже допускает наличие только одного владельца. Только один поток может получить блокировку и иметь доступ к защищаемым Mutex синхронизированным областям кода. У Mutex имеется несколько конструкторов. Ниже приведены три наиболее употребительных конструктора:
public Mutex()
public Mutex(bool initiallyOwned);
public Mutex(bool initiallyOwned, string name_mutex)
В первой форме конструктора создается Mutex, которым первоначально никто не владеет. А во второй и третей форме исходным состоянием Mutex завладевает вызывающий поток, если параметр initiallyOwned имеет логическое значение true, если false, то объектом Mutex никто не владеет.
Для того чтобы получить Mutex, используется метод WaitOne(). Метод WaitOne() ожидает до тех пор, пока не будет получен Mutex, для которого он был вызван. Следовательно, этот метод блокирует выполнение вызывающего потока до тех пор, пока не станет доступным указанный Mutex. Данный метод всегда возвращает логическое значение true. Форма объявления метода WaitOne():
Mutex mutex = new Mutex(false);
mutex.WaitOne();
Когда в коде не требуется использовать Mutex, он освобождается с помощью метода ReleaseMutex():
mutex.ReleaseMutex();
Ниже продемонстрирован код реализующий работу Mutex:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace Mutext
{
class Program
{
private static Mutex mut = new Mutex();
private const int numIterations = 1;
private const int numThreads = 3;
private static void Main(string[] args)
{
// Создаем потоки, которые будут использовать защищенный ресурс
for (int i = 0; i < numThreads; i++)
{
Thread myThread = new Thread(new ThreadStart(MyThreadProc));
myThread.Name = String.Format("Поток{0}", i + 1);
myThread.Start();
}
// Главный поток завершил работу
}
private static void MyThreadProc()
{
for (int i = 0; i < numIterations; i++)
{
UseResource();
}
}
//Синхронизируем данный метод
private static void UseResource()
{
mut.WaitOne();
Console.WriteLine("{0} зашел в защищенную зону",
Thread.CurrentThread.Name);
// Имитируем работу
Thread.Sleep(500);
Console.WriteLine("{0} покинул защищенную зону",
Thread.CurrentThread.Name);
// Release the Mutex.
mut.ReleaseMutex();
Console.ReadLine();
}
}}
Результат работы программы, с использованием Mutex, представлен на Рис. 5.5.
увеличить изображение
Рис. 5.5.Результат выполнения программы с использованием синхронизации Mutex
Если же закомментировать методы WaitOne() и ReleaseMutex() и запустить программу, то программа сгенерирует результат, представленный на Рис. 5.6.
увеличить изображение
Рис. 5.6.Результат выполнения программы без использования синхронизации Mutex
Класс Semaphore
Класс Semaphore предназначен для управления доступом к пулу ресурсов. Потоки производят вход в семафор, вызывая метод WaitOne(), и освобождают семафор при вызове метода Release(). Semaphore похож на Mutex, за исключением того, что он предоставляет одновременный доступ к общему ресурсу не одному, а нескольким потокам. Счетчик на семафоре уменьшается на единицу каждый раз, когда в семафор входит поток, и увеличивается на единицу, когда поток освобождает семафор. Когда счетчик равен нулю, последующие запросы блокируются, пока другие потоки не освободят семафор. Когда семафор освобожден всеми потоками, счетчик имеет максимальное значение, заданное при создании семафора.
Семафоры полезны в тех случаях, когда общий ресурс состоит из группы или пула ресурсов. Например, пул ресурсов может состоять из целого ряда сетевых соединений, каждое из которых служит для передачи данных. Поэтому потоку, которому требуется сетевое соединение, все равно, какое именно соединение он получит. В данном случае семафор обеспечивает удобный механизм управления доступом к сетевым соединениям.
Ниже приведена форма конструктора данного класса:
public Semaphore(int initialCount, int maximumCount)
где initialCount - это первоначальное значение для счетчика разрешений семафора, т.е. количество первоначально доступных разрешений;
maximumCount - максимальное значение данного счетчика, т.е. максимальное количество разрешений, которые может дать семафор.
Семафор применяется таким же образом, как и Mutex. В целях получения доступа к ресурсу в коде программы используется метод WaitOne() для семафора. Этот метод ожидает до тех пор, пока не будет получен семафор, для которого он вызывается. Таким образом, он блокирует выполнение вызывающего потока до тех пор, пока указанный семафор не предоставит разрешение на доступ к ресурсу.
Если коду больше не требуется владеть семафором, он освобождает его, вызывая метод Release(). Ниже приведены две формы этого метода:
public int Release()
public int Release(int releaseCount)
В первой форме метод Release() высвобождает только одно разрешение, а во второй форме - количество разрешений, определяемых параметром releaseCount. В обеих формах данный метод возвращает подсчитанное количество разрешений, существовавших до высвобождения.
Класс Barrier
Класс Barrier - это сигнальная конструкция, которая появилась в .Net Framework 4.0. Он реализует барьер потока исполнения, который позволяет множеству потоков встречаться в определенном месте во времени. Данный метод применяется для участников, нуждающихся в синхронизации, до тех пор, пока задание остается активным, динамически могут добавляться дополнительные участники, например, дочерние задачи, создаваемые из родительской задачи. Эти участники могут ожидать, пока все остальные участники не выполнят свою работу. Этот класс эффективный, поскольку построен на основе Wait(), Pulse() и спин-блокировок.
Для использования этого класса необходимо:
1. Создать экземпляр, указав количество потоков, которые будут встречаться одновременно;
2. Каждый поток, должен вызывать метод SignalAndWait().
Метод SignalAndWait() cсообщает, что участник достиг барьера (Barrier) и ожидает достижения барьера другими участниками. Ниже приведена форма этого метода:
public void SignalAndWait()
Пример использования класса Barrier приведен ниже:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace BarrierProgram
{
class Program
{
static Barrier barrier = new Barrier(3);
static void Main(string[] args)
{
new Thread(Speak).Start();
new Thread(Speak).Start();
new Thread(Speak).Start();
}
static void Speak()
{
for (int i = 0; i < 5; i++)
{
Console.Write(i + " ");
barrier.SignalAndWait();
}
Console.ReadLine();
}
}
}
Как видно из результата выполнения программы (Рис. 5.7), каждый из трех потоков выводит числа от 0 до 4, одновременно с другими потоками.
увеличить изображение
Рис. 5.7.Результат выполнения программы с использованием класса Barrier
Если же не использовать класс Barrier ,а именно убрать из кода данную строчку: barrier.SignalAndWait() - программа выведет числа в случайном порядке (Рис. 5.8).
увеличить изображение
Рис. 5.8.Результат выполнения программы без использования класса Barrier
Дата добавления: 2017-06-02; просмотров: 818;