- •А.А. Волосевич
- •Содержание
- •1. Работа с числами
- •2. Представление даты и времени
- •3. Работа со строками и текстом
- •4. Преобразование информации
- •5. Сравнение для выяснения равенства
- •6. Сравнение для выяснения порядка
- •7. Жизненный цикл объектов
- •7.1. Алгоритм сборки мусора
- •7.2. Финализаторы и интерфейс iDisposable
- •7.3. Слабые ссылки
- •8. Перечислители и итераторы
- •9. Стандартные интерфейсы коллекций
- •10. Массивы и класс System.Array
- •11. Типы для работы с коллекциями-списками
- •12. Типы для работы с коллекциями-множествами
- •13. Типы для работы с коллекциями-словарями
- •14. Типы для создания пользовательских коллекций
- •15. Технология linq to Objects
- •1. Оператор условия Where().
- •2. Операторы проекций.
- •3. Операторы упорядочивания.
- •4. Оператор группировки GroupBy().
- •5. Операторы соединения.
- •6. Операторы работы с множествами.
- •7. Операторы агрегирования.
- •8. Операторы генерирования.
- •9. Операторы кванторов и сравнения.
- •10. Операторы разбиения.
- •11. Операторы элемента.
- •12. Операторы преобразования.
- •16. Работа с объектами файловой системы
- •17. Ввод и вывод информации
- •17.1. Потоки данных и декораторы потоков
- •2. Классы для работы с потоками, связанными с хранилищами.
- •3. Декораторы потоков.
- •4. Адаптеры потоков.
- •17.2. Адаптеры потоков
- •18. Основы xml
- •19. Технология linq to xml
- •20. Дополнительные возможности обработки xml
- •21. Сериализация времени выполнения
- •22. Контракты данных и xml-сериализация
- •23. Состав и взаимодействие сборок
- •24. Метаданные и получение информации о типах
- •25. Позднее связывание и кодогенерация
- •26. Атрибуты
- •27. Динамическое связывание
- •28. Файлы конфигурации
- •29. Диагностика и мониторинг
- •30. Процессы и домены
- •31. Основы многопоточного программирования
- •32. Синхронизация потоков
- •32.1. Критические секции
- •32.2. Синхронизация на основе подачи сигналов
- •32.3. Неблокирующие средства синхронизации
- •32.4. Разделение данных между потоками
- •33. Библиотека параллельных задач
- •33.1. Параллелизм на уровне задач
- •33.2. Параллелизм при императивной обработке данных
- •33.3. Параллелизм при декларативной обработке данных
- •33.4. Обработка исключений и отмена выполнения задач
- •33.5. Коллекции, поддерживающие параллелизм
- •34. Асинхронный вызов методов
- •Литература
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