|
Вопрос по Курсу: "Параллельное программирование с использованием 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
