Вопрос по Курсу: "Параллельное программирование с использованием MS VisualStudia 2010". При компиляции Самостоятельного задания (одновременная отрисовка прямоугольников, эллипсов и выдача в текст-бокс случайного числа) среда предупреждает: suspend - устаревшая команда; примените monitor, mutex и т.п. Создаётся впечатление, что Задание создано в более поздней среде, чем VS 2010. |
Параллельные коллекции. Низкоуровневая синхронизация
SpinLock
SpinLock - структура, которая представляет низкоуровневый взаимоисключающий примитив синхронизации, выполняющий цикл в ожидании получения блокировки. Использование структуры SpinLock аналогично использованию обычной блокировки (lock), но имеет ряд отличий:
- Спин-блокировки являются структурами;
- Спин-блокировки не поддерживают реентерабельность (reentrance), это означает, что нельзя вызвать дважды метод Enter() одного и того же объекта SpinLock в одном и том же потоке, в противном случае - это приведет к генерации исключения (если включено отслеживание владельца (owner tracking)) или к взаимоблокировке (deadlock) (если отслеживание владельца отключено). Можно также включить отслеживание владельца при создании объекта спин-блокировки, однако это приведет к снижению производительности.
- SpinLock позволяет узнать, захвачена ли блокировка, с помощью свойства IsHeld, и включено ли отслеживание владельца, с помощью свойства IsHeldByCurrentThread.
- SpinLock, также, отличается от структуры lock тем, что при вызове метода Enter() используют шаблон для надежной передачи аргумента lockTaken (блок try/finally см. пример).
Пример использования шаблона SpinLock приведен ниже:
SpinLock spinLock = new SpinLock (true); // Разрешаем отслеживание владельца bool lockTaken = false; try { spinLock.Enter (ref lockTaken); // Какое-то действие } finally { if (lockTaken) spinLock.Exit(); }Листинг 15.1.
Как и при использовании обычной блокировки (lock), значение булевой переменной lockTaken после вызова метода Enter() будет равным false в случае, если метод сгенерирует исключение и блокировка не будет захвачена. Это происходит в тех случаях, когда вызывается метод Abort() в текущем потоке или генерируется исключение OutOfMemoryException, и позволяет точно знать, нужен ли последующий вызов метода Exit().В Табл. 15.1 представлены основные свойства и методы, SplinLock - структуры.
Имя | Описание |
---|---|
IsHeld | Свойство, которое позволяет, получить значение, определяющее, имеет ли какой-либо поток блокировку в настоящий момент. |
IsHeldByCurrentThread | Свойство получает значение, определяющее, имеет ли текущий поток блокировку. |
IsThreadOwnerTrackingEnabled | Свойство получает значение, указывающее, включено ли отслеживание владельца потока для данного экземпляра. |
Enter() | Метод, который получает блокировку надежным способом, то есть даже если в вызове метода возникает исключение, lockTaken можно надежно изучить и определить, была ли получена блокировка. |
Exit(), Exit(Boolean) | Метод, снимающий блокировку. |
TryEnter(Boolean), TryEnter(Int32, Boolean), TryEnter(TimeSpan, Boolean) | Метод пытается получить блокировку надежным способом, то есть даже если в вызове метода возникает исключение, lockTaken можно изучить и определить, была ли получена блокировка. |
Ниже приведен пример использования структуры Spinlock совместно с оператором lock:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Diagnostics; namespace SpinLockExample { class Program { static int N = 1000000; static Queue<Data> _queue = new Queue<Data>(); static object _lock = new Object(); static SpinLock _spinlock = new SpinLock(); //создаем класс Data class Data { public string Name { get; set; } public double Number { get; set; } } //создаем метод, добавляющий в коллекцию элементы с использованием структуры SpinLock private static void UpdateWithSpinLock(Data d, int i) { bool lockTaken = false; try { _spinlock.Enter(ref lockTaken); _queue.Enqueue(d); } finally { if (lockTaken) _spinlock.Exit(false); } } //создаем метод, выполняющий две одинаковых операции параллельно, которые вызывают метод UpdateWithSpinLock private static void UseSpinLock() { Stopwatch sw = Stopwatch.StartNew(); //создаем объект класса таймер и запускаем его Parallel.Invoke( () => { for (int i = 0; i < N; i++) { UpdateWithSpinLock(new Data() { Name = i.ToString(), Number = i }, i); //вызываем метод UpdateWithSpinLock и передаем в него параметры } }, () => { for (int i = 0; i < N; i++) { UpdateWithSpinLock(new Data() { Name = i.ToString(), Number = i }, i); ); //вызываем метод UpdateWithSpinLock и передаем в него параметры } } ); sw.Stop();//оставливаем таймер Console.WriteLine("Затраченное время в мс при использовании структуры spinlock: {0}", sw.ElapsedMilliseconds);//выводим на экран результаты работы таймера в мс } //создаем метод, добавляющий в коллекцию элементы с использованием оператора Lock static void UpdateWithLock(Data d, int i) { lock (_lock) { _queue.Enqueue(d); } } //создаем метод, выполняющий две одинаковых операции параллельно, которые вызывают метод UseLock private static void UseLock() { Stopwatch sw = Stopwatch.StartNew();//создаем объект класса таймер и запускаем его Parallel.Invoke( () => { for (int i = 0; i < N; i++) { UpdateWithLock(new Data() { Name = i.ToString(), Number = i }, i); ); //вызываем метод UpdateWithLock и передаем в него параметры } }, () => { for (int i = 0; i < N; i++) { UpdateWithLock(new Data() { Name = i.ToString(), Number = i }, i); } } ); //вызываем метод UpdateWithLock и передаем в него параметры sw.Stop();//оставливаем таймер Console.WriteLine("Затраченное время в мс при использовании оператора lock: {0}", sw.ElapsedMilliseconds); ) //выводим на экран результаты работы таймера в мс } static void Main(string[] args) { UseLock(); //вызываем метод UseLock _queue.Clear(); //очищаем коллекцию UseSpinLock();//вызываем метод UseSpinLock Console.ReadLine(); } }}
Как видно из результатов выполнения данного примера (Рис. 15.1), алгоритм использующий структуру Spinlock выполнит операцию быстрее, т.к выполняется минимальный объем работ в критическом фрагменте кода (добавление в коллекцию элементов). Увеличивая объем работы, небольшой объект повышает производительность SpinLock в сравнении со стандартной блокировкой, при этом следует отметить, что SpinLock более ресурсоемкий в отличие от стандартной блокировки.
увеличить изображение
Рис. 15.1. Результат выполнения программы, которая использует для синхронизации структуру SpinLock и оператор lock