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

Синхронизация потоков

< Лекция 3 || Лекция 4: 12345 || Лекция 5 >

Класс Semaphore

Класс Semaphore предназначен для управления доступом к пулу ресурсов. Потоки производят вход в семафор, вызывая метод WaitOne(), и освобождают семафор при вызове метода Release(). Semaphore похож на Mutex, за исключением того, что он предоставляет одновременный доступ к общему ресурсу не одному, а нескольким потокам. Счетчик на семафоре уменьшается на единицу каждый раз, когда в семафор входит поток, и увеличивается на единицу, когда поток освобождает семафор. Когда счетчик равен нулю, последующие запросы блокируются, пока другие потоки не освободят семафор. Когда семафор освобожден всеми потоками, счетчик имеет максимальное значение, заданное при создании семафора.

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

Ниже приведена форма конструктора данного класса:

public Semaphore(int initialCount, int maximumCount)

где initialCount - это первоначальное значение для счетчика разрешений семафора, т.е. количество первоначально доступных разрешений;

maximumCount - максимальное значение данного счетчика, т.е. максимальное количество разрешений, которые может дать семафор.

Семафор применяется таким же образом, как и Mutex. В целях получения доступа к ресурсу в коде программы используется метод WaitOne() для семафора. Этот метод ожидает до тех пор, пока не будет получен семафор, для которого он вызывается. Таким образом, он блокирует выполнение вызывающего потока до тех пор, пока указанный семафор не предоставит разрешение на доступ к ресурсу.

Если коду больше не требуется владеть семафором, он освобождает его, вызывая метод Release(). Ниже приведены две формы этого метода:

public int Release()
public int Release(int releaseCount)

В первой форме метод Release() высвобождает только одно разрешение, а во второй форме - количество разрешений, определяемых параметром releaseCount. В обеих формах данный метод возвращает подсчитанное количество разрешений, существовавших до высвобождения.

Класс Barrier

Класс Barrier - это сигнальная конструкция, которая появилась в .Net Framework 4.0. Он реализует барьер потока исполнения, который позволяет множеству потоков встречаться в определенном месте во времени. Данный метод применяется для участников, нуждающихся в синхронизации, до тех пор, пока задание остается активным, динамически могут добавляться дополнительные участники, например, дочерние задачи, создаваемые из родительской задачи. Эти участники могут ожидать, пока все остальные участники не выполнят свою работу. Этот класс эффективный, поскольку построен на основе Wait(), Pulse() и спин-блокировок.

Для использования этого класса необходимо:

  1. Создать экземпляр, указав количество потоков, которые будут встречаться одновременно;
  2. Каждый поток, должен вызывать метод SignalAndWait().

Метод SignalAndWait() cсообщает, что участник достиг барьера (Barrier) и ожидает достижения барьера другими участниками. Ниже приведена форма этого метода:

public void SignalAndWait()

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace BarrierProgram
{
    class Program
    {
        static Barrier barrier = new Barrier(3);
        static void Main(string[] args)
        {
            new Thread(Speak).Start();
            new Thread(Speak).Start();
            new Thread(Speak).Start();
        }
        static void Speak()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.Write(i + " ");
                barrier.SignalAndWait();
            }
            Console.ReadLine();
        }
    }
}

Как видно из результата выполнения программы (Рис. 5.7), каждый из трех потоков выводит числа от 0 до 4, одновременно с другими потоками.

 Результат выполнения программы с использованием класса Barrier

увеличить изображение
Рис. 5.7. Результат выполнения программы с использованием класса Barrier

Если же не использовать класс Barrier ,а именно убрать из кода данную строчку: barrier.SignalAndWait() - программа выведет числа в случайном порядке (Рис. 5.8).

 Результат выполнения программы без использования класса Barrier

увеличить изображение
Рис. 5.8. Результат выполнения программы без использования класса Barrier

Класс ReaderWriterLockSlim

Класс ReaderWriterLockSlim позволяет нескольким потокам находиться в режиме чтения, одному потоку - в режиме записи с монопольным владением блокировкой, и одному потоку с доступом на чтение находиться в обновляемом режиме чтения, откуда поток может перейти в режим записи без необходимости отказываться от доступа на чтение к ресурсу. Класс ReaderWriterLockSlim содержит следующие методы для получения блокировки:

  • Чтения. EnterReadLock() и TryEnterReadLock()
  • Записи. EnterWriteLock() и TryEnterWriteLock().

Если задача сначала выполняет чтение и лишь затем запись, она может получать обновляемую блокировку чтения с помощью EnterUpgradableReadLock() и TryEnterUpgradableReadLock(). Также в этом классе имеется несколько свойств, которые позволяют получать информацию об удерживаемых блокировках: CurrentReadCount, WaitingReadCount, WaitingUpgradableReadCount и WaitingWriteCount.

События синхронизации потоков

В .NET Framework можно также использовать события для синхронизации потоков программы. ManualResetEvent, AutoResetEvent, ManualResetEventSlim и CountdownEvent, которые находятся в пространстве имен System.Threading.

Классы ManualResetEventSlim и CountdownEvent появились в версии .NET Framework 4.0. Эти классы являются производными от класса EventWaitHandle, находящегося на верхнем уровне иерархии классов, и применяются в тех случаях, когда один поток ожидает появления некоторого события в другом потоке. Как только такое событие появляется, второй поток уведомляет о нем первый поток, позволяя тем самым возобновить его выполнение.

Ниже приведены конструкторы классов ManualResetEvent и AutoResetEvent:

public ManualResetEvent(bool initialState)
public AutoResetEvent(bool initialState)

- где initialState параметр имеет логическое значение true, то о событии первоначально уведомляется.

  • Событие ManualResetEvent позволяет потокам взаимодействовать друг с другом путем передачи сигналов. Обычно это взаимодействие касается задачи, которую один поток должен завершить до того, как другой продолжит работу. Когда поток начинает работу, которая должна быть завершена до продолжения работы других потоков, он вызывает метод Reset() для того, чтобы поместить ManualResetEvent в несигнальное состояние. Этот поток можно понимать как контролирующий ManualResetEvent. Потоки, которые вызывают метод WaitOne() в ManualResetEvent, будут заблокированы, ожидая сигнала. Когда контролирующий поток завершит работу, он вызовет метод Set() для сообщения о том, что ожидающие потоки могут продолжить работу.
  • Событие AutoResetEvent отличается от события типа ManualResetEvent лишь способом установки в исходное состояние. Если для события типа ManualResetEvent событийный объект остается в сигнальном состоянии до тех пор, пока не будет вызван метод Reset(), то для события типа AutoResetEvent событийный объект автоматически переходит в несигнальное состояние, как только поток, ожидающий это событие, получит уведомление о нем и возобновит свое выполнение. Поэтому если применяется событие типа AutoResetEvent, то вызывать метод Reset() необязательно.
  • Событие ManualResetEventSlim переводится в сигнальное состояние вызовом метода Set(), а с помощью Reset() возвращается обратно в несигнальное состояние. В случае вызова метода Set() при наличии множества потоков, ждущих перехода события в сигнальное состояние, ожидание всех этих потоков немедленно прекращается. В случае, если поток просто вызывает метод WaitOne(), а событие уже находится в сигнальном состоянии, ожидавший поток может сразу же продолжить работу.
< Лекция 3 || Лекция 4: 12345 || Лекция 5 >
Владимир Каширин
Владимир Каширин

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

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

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