← Назад к вопросам

Что такое семафор?

2.2 Middle🔥 101 комментариев
#Конкурентность и горутины

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое семафор?

Семафор — это механизм управления доступом к общим ресурсам в многозадачных или многопоточных системах. Его основная функция — контроль над одновременным использованием ограниченного количества ресурсов (например, файлов, сетевых подключений, блоков памяти). Семафор работает как счетчик, который определяет, сколько потоков или процессов могут параллельно выполнять определенную операцию или использовать ресурс.

Основные принципы работы

В основе классического семафора лежит целочисленная переменная и две атомарные операции:

  1. Операция "захвата" (acquire, lock или P) — уменьшает значение счетчика. Если после уменьшения счетчик становится отрицательным, поток блокируется до его увеличения.
  2. Операция "освобождения" (release, unlock или V) — увеличивает значение счетчика, потенциально разрешая заблокированным потокам продолжить работу.

В языке Go семафоры реализуются не через отдельную структуру с именем "semaphore", а через мощные механизмы, предоставленные пакетом sync. Однако абстрактная концепция широко применяется.

Типы семафоров и их реализация в Go

1. Бинарный семафор (Mutex)

Это семафор, счетчик которого принимает только два значения (0 и 1). Он используется для обеспечения взаимного исключения (mutual exclusion) и фактически является мьютексом (mutex) — гарантирует, что только один поток может выполнять критическую секцию кода в данный момент.

В Go бинарный семафор реализуется через sync.Mutex.

package main

import (
    "fmt"
    "sync"
    "time"
)

var counter int
var mu sync.Mutex // Бинарный семафор (мьютекс)

func increment() {
    mu.Lock()   // Операция P (захват)
    counter++
    mu.Unlock() // Операция V (освобождение)
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func() {
            increment()
            wg.Done()
        }()
    }
    wg.Wait()
    fmt.Println("Final counter:", counter)
}

2. Счетный семафор (Counting Semaphore)

Это семафор, который позволяет задать максимальное количество одновременных "захватов". Он идеально подходит для ограничения количества одновременных операций (например, горутин, выполняющих HTTP-запросы к API с ограничением rate limit).

В Go классический счетный семафор можно эмулировать с помощью канала с буфером (buffered channel) определенной емкости. Каждая горутина пытается отправить значение в канал (захват) и затем получить его (освобождение).

package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, sem chan struct{}, wg *sync.WaitGroup) {
    sem <- struct{}{} // Acquire: отправка в канал (блокируется если буфер полон)
    fmt.Printf("Worker %d started\n", id)
    time.Sleep(500 * time.Millisecond) // Имитация работы
    fmt.Printf("Worker %d finished\n", id)
    <-sem // Release: чтение из канала (освобождает место)
    wg.Done()
}

func main() {
    const maxConcurrent = 3 // Максимальное количество одновременных горутин
    sem := make(chan struct{}, maxConcurrent)

    var wg sync.WaitGroup
    for i := 1; i <= 10; i++ {
        wg.Add(1)
        go worker(i, sem, &wg)
    }
    wg.Wait()
    close(sem)
}

Семафоры в стандартной библиотеке Go

Go предоставляет более специализированные и высокоуровневые инструменты для управления параллельностью, которые реализуют концепцию семафора:

  • sync.WaitGroup: Можно рассматривать как семафор, где счетчик — количество активных задач. Метод Add() увеличивает счетчик, Done() уменьшает его, а Wait() блокируется до нуля.
  • context.Context с каналами: Часто используется для сигнализации и ограничения операций.
  • sync/errgroup.Group: Расширяет WaitGroup, добавляя обработку ошибок.

Ключевые различия между мьютексом и счетным семафором

  • Мьютекс (sync.Mutex) строго обеспечивает взаимное исключение — доступ только одного потока.
  • Счетный семафор позволяет контролировать доступ N потоков к ресурсу, где N может быть больше 1. Это делает его более гибким для сценариев, где ресурс допускает ограниченную параллельность (например, пул соединений с базой данных).

Практическое применение семафоров в Go

  1. Ограничение количества одновременных запросов к внешнему API.
  2. Управление пулом ресурсов (например, ограничение количества открытых файлов или сетевых сокетов).
  3. Координация этапов в параллельных алгоритмах, где следующая фаза может начаться только после завершения определенного числа задач предыдущей фазы.

Таким образом, хотя в Go нет структуры с именем Semaphore, его философия "не заставлять, а предоставлять возможности" позволяет легко реализовать эту концепцию через каналы и примитивы пакета sync. Понимание семафоров является фундаментальным для создания корректных, эффективных и безопасных многопоточных приложений.

Что такое семафор? | PrepBro