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

