Семафоры


Семафоры являются еще одним инструментом, который предлагает нам платформа .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(2, 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 покидает библиотеку