Томский политехнический университет
Опубликован: 23.01.2013 | Доступ: свободный | Студентов: 1157 / 192 | Длительность: 12:09:00
Лекция 11:

Параллельные коллекции. Низкоуровневая синхронизация

Аннотация: В рамках данной лекции будут рассмотрены следующие вопросы: SpinLock; SpinWait; параллельные коллекции; ConcurrentQueue; ConcurrentStack; ConcurrentBag; ConcurrentDictionary; BlockingCollection; создание экземпляра класса BlockingCollection; создание поставщика; создание потребителя.

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 - структуры.

Таблица 15.1. Основные свойства и методы SpinLock - структуры
Имя Описание
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 более ресурсоемкий в отличие от стандартной блокировки.

 Результат выполнения программы, которая использует для синхронизации структуру SpinLock и оператор lock

увеличить изображение
Рис. 15.1. Результат выполнения программы, которая использует для синхронизации структуру SpinLock и оператор lock
Владимир Каширин
Владимир Каширин

Вопрос по Курсу: "Параллельное программирование с использованием MS VisualStudia 2010".

При компиляции Самостоятельного задания (одновременная отрисовка прямоугольников, эллипсов и выдача в текст-бокс случайного числа) среда предупреждает: suspend - устаревшая команда; примените monitor, mutex и т.п.

Создаётся впечатление, что Задание создано в более поздней среде, чем VS 2010.