Семафоры являются еще одним инструментом, который предлагает нам платформа .NET для управления синхронизацией. Семафоры позволяют ограничить количество потоков, которые имеют доступ к определенным ресурсам. В .NET семафоры представлены классом Semaphore.
Для создания семафора применяется один из конструкторов класса Semaphore:
Semaphore (int initialCount, int maximumCount): параметр initialCount
задает начальное количество потоков,
а maximumCount
- максимальное количество потоков, которые имеют доступ к общим ресурсам
Semaphore (int initialCount, int maximumCount, string? name): в дополнение задает имя семафора
Semaphore (int initialCount, int maximumCount, string? name, out bool createdNew): последний параметр
- createdNew
при значении true указывает, что новый семафор был успешно создан. Если этот параметр равен false, то семафор с указанным именем уже
существует
Для работы с потоками класс Semaphore имеет два основных метода:
WaitOne(): ожидает получения свободного места в семафоре
Release(): освобождает место в семафоре
Например, у нас такая задача: есть некоторое число читателей, которые приходят в библиотеку три раза в день и что-то там читают. И пусть у нас будет ограничение, что единовременно в библиотеке не может находиться больше трех читателей. Данную задачу очень легко решить с помощью семафоров:
// запускаем пять потоков for (int i = 1; i < 6; i++) { Reader reader = new Reader(i); } class Reader { // создаем семафор static Semaphore sem = new Semaphore(3, 3); Thread myThread; int count = 3;// счетчик чтения public Reader(int i) { myThread = new Thread(Read); myThread.Name = $"Читатель {i}"; myThread.Start(); } public void Read() { while (count > 0) { sem.WaitOne(); // ожидаем, когда освободиться место Console.WriteLine($"{Thread.CurrentThread.Name} входит в библиотеку"); Console.WriteLine($"{Thread.CurrentThread.Name} читает"); Thread.Sleep(1000); Console.WriteLine($"{Thread.CurrentThread.Name} покидает библиотеку"); sem.Release(); // освобождаем место count--; Thread.Sleep(1000); } } }
В данной программе читатель представлен классом Reader. Он инкапсулирует всю функциональность, связанную с потоками, через переменную
Thread myThread
.
Сам семафор определяется в виде статической переменной sem:
static Semaphore sem = new Semaphore(3, 3);.
Его конструктор принимает два параметра: первый указывает, какому числу объектов изначально будет доступен семафор, а второй параметр указывает, какой максимальное число объектов будет использовать данный семафор. В данном случае у нас только три читателя могут одновременно находиться в библиотеке, поэтому максимальное число равно 3.
Основной функционал сосредоточен в методе Read
, который и выполняется в потоке. В начале для ожидания получения семафора
используется метод sem.WaitOne()
:
sem.WaitOne(); // ожидаем, когда освободиться место
После того, как в семафоре освободится место, данный поток заполняет свободное место и начинает выполнять все дальнейшие действия.
После окончания чтения мы высвобождаем семафор с помощью метода sem.Release()
:
sem.Release(); // освобождаем место
После этого в семафоре освобождается одно место, которое заполняет другой поток.
Пример консольного вывода:
Читатель 5 входит в библиотеку Читатель 5 читает Читатель 4 входит в библиотеку Читатель 4 читает Читатель 1 входит в библиотеку Читатель 1 читает Читатель 5 покидает библиотеку Читатель 1 покидает библиотеку Читатель 4 покидает библиотеку Читатель 3 входит в библиотеку Читатель 3 читает Читатель 2 входит в библиотеку Читатель 2 читает Читатель 4 входит в библиотеку Читатель 3 покидает библиотеку Читатель 2 покидает библиотеку Читатель 5 входит в библиотеку Читатель 5 читает Читатель 4 читает Читатель 1 входит в библиотеку Читатель 1 читает Читатель 5 покидает библиотеку Читатель 3 входит в библиотеку Читатель 3 читает Читатель 4 покидает библиотеку Читатель 1 покидает библиотеку Читатель 2 входит в библиотеку Читатель 2 читает Читатель 3 покидает библиотеку Читатель 5 входит в библиотеку Читатель 5 читает Читатель 2 покидает библиотеку Читатель 1 входит в библиотеку Читатель 4 входит в библиотеку Читатель 1 читает Читатель 4 читает Читатель 5 покидает библиотеку Читатель 1 покидает библиотеку Читатель 4 покидает библиотеку Читатель 2 входит в библиотеку Читатель 3 входит в библиотеку Читатель 2 читает Читатель 3 читает Читатель 3 покидает библиотеку Читатель 2 покидает библиотеку