Что такое примитив синхронзиации semaphore?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое семафор (Semaphore)?
Семафор — это примитив синхронизации, используемый для контроля доступа к общему ресурсу в многопоточной или многопроцессорной среде. Он управляет доступом через счётчик, который представляет количество доступных "разрешений". Потоки могут запрашивать разрешение (уменьшая счётчик) и освобождать его (увеличивая счётчик). Если разрешений нет, поток блокируется до их появления.
В отличие от мьютекса, который является эксклюзивным (только один поток владеет доступом), семафор позволяет одновременно работать нескольким потокам, ограничивая их максимальное количество.
Основные операции с семафором
- Инициализация: Устанавливается начальное количество разрешений.
- Ожидание / Захват (Wait): Поток запрашивает разрешение. Если счётчик > 0, он уменьшается, и поток продолжает работу. Если счётчик = 0, поток блокируется.
- Освобождение (Release): Поток возвращает разрешение, увеличивая счётчик и потенциально разблокируя ожидающие потоки.
Типы семафоров
- Счётчиковый семафор: Может иметь любое неотрицательное значение счётчика, используется для ограничения доступа к пулу ресурсов (например, подключений к БД).
- Двоичный семафор: Частный случай, где счётчик равен 0 или 1. Функционально близок к мьютексу, но с ключевым отличием: семафор может быть освобождён любым потоком, а не только тем, который его захватил.
Семафор в C# (System.Threading.Semaphore и SemaphoreSlim)
В C# есть два основных класса для работы с семафорами:
SemaphoreSlim (рекомендуется для внутрипроцессного использования)
Легковесная версия, оптимизированная для работы в рамках одного процесса.
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
// Семафор, позволяющий одновременно работать не более 3 потокам
static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(initialCount: 3);
static async Task Main()
{
Task[] tasks = new Task[10];
for (int i = 0; i < tasks.Length; i++)
{
int taskId = i;
tasks[i] = Task.Run(() => AccessDatabase(taskId));
}
await Task.WhenAll(tasks);
Console.WriteLine("Все задачи завершены.");
}
static async Task AccessDatabase(int id)
{
Console.WriteLine($"Задача {id} ждёт доступа к БД...");
await _semaphore.WaitAsync(); // Асинхронное ожидание разрешения
try
{
Console.WriteLine($"Задача {id} начала работу с БД. [Разрешённых потоков: {3 - _semaphore.CurrentCount}]");
await Task.Delay(1000); // Имитация работы
}
finally
{
_semaphore.Release(); // Критически важно освободить семафор в finally
Console.WriteLine($"Задача {id} освободила семафор.");
}
}
}
Semaphore (для межпроцессной синхронизации)
Более тяжеловесный класс, который может синхронизировать потоки между разными процессами на одном компьютере (именованный семафор).
// Создание или открытие именованного системного семафора
using (var semaphore = new Semaphore(initialCount: 2, maximumCount: 2, name: "MyGlobalSemaphore"))
{
Console.WriteLine("Ожидание доступа к межпроцессному ресурсу...");
semaphore.WaitOne();
try
{
// Работа с общим ресурсом между процессами
Console.WriteLine("Ресурс захвачен. Работаем...");
Thread.Sleep(2000);
}
finally
{
semaphore.Release();
Console.WriteLine("Ресурс освобождён.");
}
}
Ключевые аспекты использования
- Идиома
Wait/Releaseвtry-finally: Всегда освобождайте семафор в блокеfinally, чтобы гарантировать его освобождение даже при возникновении исключения. - Асинхронное ожидание:
SemaphoreSlimпредоставляет методWaitAsync(), который следует использовать в асинхронном коде для избежания блокировки потоков. - Отличие от мьютекса: Семафор не имеет концепции "владельца" — любой поток может вызвать
Release(). Мьютекс (Mutexилиlock) должен освобождаться тем же потоком, который его захватил. - Типичные сценарии применения:
* Ограничение количества одновременных подключений к базе данных или внешнему API.
* Управление доступом к пулу ресурсов (например, сокетов или файловых дескрипторов).
* Реализация паттерна "Producer-Consumer" с ограниченной ёмкостью буфера.
Важные предостережения
- Не допускайте утечек разрешений: Каждый вызов
Wait()/WaitAsync()должен иметь парный вызовRelease(). Неосвобождение семафора приводит к "зависанию" потоков или постепенному исчерпанию ресурсов. - Инициализация: Начальное значение счётчика (
initialCount) не должно превышать максимальное (maximumCount). - Производительность: Для синхронизации в рамках одного процесса всегда предпочитайте
SemaphoreSlim— он значительно быстрееSemaphore.
Таким образом, семафор является гибким и мощным инструментом для управления параллельным доступом, когда необходимо ограничить, но не полностью исключить одновременную работу потоков над общим ресурсом. Его правильное использование — важный навык для разработки надёжных многопоточных приложений на C#.