Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Базовые технологии платформы .NET.docx
Скачиваний:
11
Добавлен:
18.08.2019
Размер:
423.24 Кб
Скачать

32.2. Синхронизация на основе подачи сигналов

При использовании синхронизации на основе подачи сигналов один поток получает уведомления от другого потока. Обычно уведомления используются для возобновления работы заблокированного потока. Для реализации данного подхода платформа .NET предлагает классы AutoResetEvent, ManualResetEvent, ManualResetEventSlim, CountdownEvent и Barrier (все они размещены в пространстве имён System.Threading).

Классы AutoResetEvent, ManualResetEvent, ManualResetEventSlim унаследованы от класса EventWaitHandle. Имея доступ к объекту EventWaitHandle, поток может вызвать его метод WaitOne(), чтобы остановиться и ждать сигнала. Для отправки сигнала применяется вызов метода Set(). Если используются ManualResetEvent и ManualResetEventSlim, все ожидающие потоки освобождаются и продолжают выполнение. При использовании AutoResetEvent ожидающие потоки освобождаются и запускаются последовательно, на манер очереди. Аналогией для AutoResetEvent является турникет, пропускающий людей по одному, а ManualResetEvent подобен воротам, которые или закрыты, или открыты.

Рис. 18. Пример работы с объектом AutoResetEvent.

Класс CountdownEvent позволяет организовать счётчик уведомлений. Конструктор класса принимает в качестве аргумента начальное значение счётчика. Вызов экземплярного метода Signal() уменьшает значение счётчика на единицу. Метод Wait() блокирует поток, пока счётчик не станет равным нулю.

private static CountdownEvent counter = new CountdownEvent(3);

public static void Main()

{

new Thread(SaySomething).Start("I am thread 1");

new Thread(SaySomething).Start("I am thread 2");

new Thread(SaySomething).Start("I am thread 3");

// ждём, пока метод Signal() не вызовется три раза

counter.Wait();

Console.WriteLine("All threads have finished speaking!");

}

private static void SaySomething(object thing)

{

Thread.Sleep(1000);

Console.WriteLine(thing);

counter.Signal();

}

Объект класса Barrier организует для нескольких потоков точку встречи во времени. Чтобы использовать Barrier, нужно вызвать его конструктор, передав количество встречающихся потоков. Затем отдельные потоки вызывают экземплярный метод Barrier.SignalAndWait(), чтобы приостановиться и продолжить выполнение после совместной встречи.

В следующем примере каждый из трёх потоков печатает числа от 0 до 4, синхронизируя работу со своими «коллегами»:

using System;

using System.Threading;

public class BarrierExample

{

private static readonly Barrier _barrier = new Barrier(3);

public static void Main()

{

new Thread(Speak).Start();

new Thread(Speak).Start();

new Thread(Speak).Start();

// вывод: 0 0 0 1 1 1 2 2 2 3 3 3 4 4 4

Console.ReadLine();

}

private static void Speak()

{

for (var i = 0; i < 5; i++)

{

Console.Write(i + " ");

_barrier.SignalAndWait();

}

}

}

32.3. Неблокирующие средства синхронизации

Неблокирующие средства синхронизации позволяют осуществлять совместный доступ к простым ресурсам нескольких потоков без блокировки, паузы или ожидания. Коротко рассмотрим неблокирующие средства синхронизации, которые предлагает платформа .NET.

Для оптимизации выполнения программы центральный процессор иногда применяет перестановку инструкций программы или их кэширование. Чтобы отменить подобную оптимизацию следует разместить в исходном коде барьеры памяти. Процессор не способен изменить порядок команд, чтобы инструкции до барьера, выполнялись после инструкций за барьером. Для установки барьера памяти нужно вызвать статический метод Thread.MemoryBarrier(). Язык C# содержит специальный модификатор volatile, применяемый при объявлении поля. Инструкции записи данных в такое не переставляются процессором в целях оптимизации.

Статический класс System.Threading.Interlocked имеет методы для инкремента, декремента и сложения аргументов типа int или long, а также методы присваивания значений числовым и ссылочным переменным. Каждый метод гарантировано выполняется как атомарная операция, без блокировки текущего потока выполнения.

int x = 10, y = 20;

Interlocked.Add(ref x, y); // x = x + y

Interlocked.Increment(ref x); // x++

Interlocked.Exchange(ref x, y); // x = y

Interlocked.CompareExchange(ref x, 50, y); // if (x == y) x = 50