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

