Вопрос по Курсу: "Параллельное программирование с использованием MS VisualStudia 2010". При компиляции Самостоятельного задания (одновременная отрисовка прямоугольников, эллипсов и выдача в текст-бокс случайного числа) среда предупреждает: suspend - устаревшая команда; примените monitor, mutex и т.п. Создаётся впечатление, что Задание создано в более поздней среде, чем VS 2010. |
Параллельные коллекции. Низкоуровневая синхронизация
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.
Имя | Описание |
---|---|
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(); } }
увеличить изображение
Рис. 15.2. Результат выполнения программы использующую для синхронизации структуру SpinWait
Параллельные коллекции
ConcurrentQueue
ConcurrentQueue представляет собой потокобезопасную коллекцию, обслуживаемую по принципу "первым поступил - первым обслужен" (FIFO). Этот класс коллекции реализован со свободным от блокировок алгоритмом и использует 32 массива, которые внутренне скомбинированы в связный список. Для доступа к элементам очереди применяются методы, представленные в Табл. 15.3. Имена этих методов схожи с методами коллекции Queue, но с добавлением префикса Try к тем из них, которые могут дать сбой. Поскольку этот класс реализует интерфейс IProducerConsumerCollection, методы TryAdd() и TryTake() аналогичны вызовам методов Enqueue() и TryDequeue().
Имя | Описание |
---|---|
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(); } } }
увеличить изображение
Рис. 15.3. Результат выполнения программы использующую коллекцию ConcurrentQueue