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

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

SpinWait

SpinWait - это тип синхронизации, который используется в низкоуровневых сценариях, чтобы избежать ресурсоемких переключений контекста и переходов в режим ядра, необходимых для событий ядра. Структуру SpinWait также можно использовать самостоятельно для базовых функций цикла в одной программе. При этом следует отметить, что структура SpinWait предназначен для обеспечения производительного параллелизма, сама по себе эта структура не является потокобезопасной, и поэтому каждый поток должен использовать свою копию этой структуры.

Существует два способа использования структуры SpinWai:

  • Первый способ - использовать метод SpinUntil:
    bool _proceed;
    void Test()
    {
      SpinWait.SpinUntil (() => { Thread.MemoryBarrier(); return _proceed; });
    }
  • Второй способ - создание экземпляра класса SpinWait с последующим вызовом метода SpinOnce() в цикле:
    bool _proceed;
    void Test()
    {
      var spinWait = new SpinWait();
      while (!_proceed) { Thread.MemoryBarrier(); spinWait.SpinOnce(); }
      ...
    }

Основные свойства и методы структуры SpinWait представлены в Табл. 15.2.

Таблица 15.2. Основные свойства и методы SpinWait
Имя Описание
Count Получает число раз, которое SpinOnce был вызван для этого экземпляра.
NextSpinWillYield Получает значение, показывающее, даст ли следующий вызов к SpinOnce
SpinOnce Выполняет одну прокрутку.
Reset Сбрасывает подсчет прокруток.
SpinUntil (Func<Boolean>) Выполняет прокрутки до удовлетворения заданного условия.

Пример использования структуры SpinWait представлен ниже:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace SpinWaitExample
{
    class Program
    {
    static bool someBoolean = false;
       static int num = 0;
       static void Main()
       {
           // создаем задачу, которая будет выполняться пока переменной someBoolean
     не присвоится значение true
           Task t1 = Task.Factory.StartNew(() =>
           {
               SpinWait sw = new SpinWait();
               while (!someBoolean)
               {
                   // NextSpinWillYield возвращает значение true если
                   // вызов метода sw.SpinOnce()даст доступ к процессору, 
                   // запуская обязательное переключение контекста.
                   if (sw.NextSpinWillYield) num++;
                sw.SpinOnce();
               }
               Console.WriteLine("SpinWait вызывается {0} раз, 
    переменная вызывается {1} раз", sw.Count, num);
           });
           // создаем вторую задачу которая ожидает 100 мс, пока переменной someBoolean 
    не присвоится значение true
           Task t2 = Task.Factory.StartNew(() =>
           {
               Thread.Sleep(100);
               someBoolean = true;
           });
           // ожидаем выполнение всех задач
           Task.WaitAll(t1, t2);
           Console.ReadLine();
       }
    }
 Результат выполнения программы использующую для синхронизации структуру SpinWait

увеличить изображение
Рис. 15.2. Результат выполнения программы использующую для синхронизации структуру SpinWait

Параллельные коллекции

ConcurrentQueue

ConcurrentQueue представляет собой потокобезопасную коллекцию, обслуживаемую по принципу "первым поступил - первым обслужен" (FIFO). Этот класс коллекции реализован со свободным от блокировок алгоритмом и использует 32 массива, которые внутренне скомбинированы в связный список. Для доступа к элементам очереди применяются методы, представленные в Табл. 15.3. Имена этих методов схожи с методами коллекции Queue, но с добавлением префикса Try к тем из них, которые могут дать сбой. Поскольку этот класс реализует интерфейс IProducerConsumerCollection, методы TryAdd() и TryTake() аналогичны вызовам методов Enqueue() и TryDequeue().

Таблица 15.3. Основные методы коллекции ConcurrentQueue
Имя Описание
Enqueue(T) Добавляет объект в конец коллекции
TryPeek(out T) Пытается удалить и вернуть объект, находящийся в начале коллекции
TryDequeue(out T) Пытается вернуть объект из начала коллекции

Пример использования коллекции ConcurrentQueue представлен ниже:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.Collections;
namespace ConcurrentQueueExample
{
    class Program
    {
    
        static void Main(string[] args)
        {
            // создаем коллекцию
            ConcurrentQueue<int> sharedQueue = new ConcurrentQueue<int>();
            // заполняем коллекцию в цикле с помощью метода Enqueue
            for (int i = 0; i < 1000; i++)
            {
                sharedQueue.Enqueue(i);
            }
            // объявляем переменную-счетчик количества обработанных элементов
            int itemCount = 0;
            // создаем список задач
            Task[] tasks = new Task[10];
            for (int i = 0; i < tasks.Length; i++)
            {
                // создаем задачу
                tasks[i] = new Task(() =>
                {
                    while (sharedQueue.Count > 0)
                    {
                        Thread.Sleep(10);
                       //объявляем переменную для запросов удаления из очереди
                        int queueElement;
                        // удаляем элемент из коллекции с помощью метода TryDequeue
                        bool gotElement = sharedQueue.TryDequeue(out queueElement);
                        // увеличиваем значение переменной и сохраняем результат
                         if (gotElement)
                        {
                        Interlocked.Increment(ref itemCount); 
                        } 
                    }
                    }
                });
                // запускаем новую задачу
                tasks[i].Start();
            }
                // ожидаем завершения всех задач
                Task.WaitAll(tasks);
                // выводим на экран отчет о количестве обработанных элементов
                Console.WriteLine("Обработанно элементов: {0}", itemCount);
                 Console.ReadLine();
        }
    }
}
 Результат выполнения программы использующую коллекцию ConcurrentQueue

увеличить изображение
Рис. 15.3. Результат выполнения программы использующую коллекцию ConcurrentQueue
Владимир Каширин
Владимир Каширин

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

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

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