Semaphore: какво е, принцип на работа и приложение в синхронизацията на нишки

Автор: IT Sectr Публикувано: 2026-03-19 Време за четене: 8 мин

Semaphore е примитив за синхронизация, който управлява достъпа до споделен ресурс чрез брояч и опашка от чакащи нишки. Според Wikipedia, 2024, семафорът е предложен от Едсгер Дайкстра през 1965 г. за решаване на проблеми с многонишковото взаимодействие. Инструментът позволява ограничаване на броя нишки, които едновременно работят с критична секция.

Основни точки

  • Semaphore — примитив за синхронизация, управляващ достъпа чрез брояч на разрешения.
  • Двоичен семафор приема стойности 0 и 1, работейки като флаг за блокиране.
  • Броячeн семафор допуска едновременен достъп на определен брой нишки.
  • За разлика от mutex, семафорът не е обвързан с нишка-собственик.
  • Deadlock — една от основните опасности при неправилна употреба на семафори.

Какво е Semaphore?

Semaphore — е примитив за синхронизация, който използва брояч за управление на достъпа до споделен ресурс. Концепцията е предложена от Едсгер Дайкстра през 1965 г. и става основа на всички съвременни механизми за синхронизация в операционните системи.

Определение и предназначение

Семафорът представлява целочислена променлива с две атомарни операции: wait (acquire) и signal (release). Операцията wait намалява брояча, а signal го увеличава. Когато броячът достигне нула, нишката, която извиква wait, се блокира до изпълнението на signal от друга нишка.

Основното предназначение на семафора е защита на критичните секции от едновременен достъп на множество нишки. За разлика от mutex, семафорът не изисква обвързване с нишката-собственик, което го прави подходящ за по-широк кръг от координационни задачи.

История и теоретична основа

Концепцията за семафора възниква в контекста на операционната система THE, разработена в Technische Hogeschool Eindhoven. Дайкстра формализира семафора като математическа абстракция, доказвайки неговата достатъчност за реализация на всякакви примитиви за синхронизация.

Как работи семафорът?

Механизмът на семафора се основава на две атомарни операции и вътрешна опашка за изчакване. При извикване на acquire нишката проверява стойността на брояча и или продължава изпълнението, или се блокира до освобождаването на ресурса.

Брояч и атомарни операции

При създаване на семафора се задава начална стойност на брояча на разрешения. Всяко извикване на acquire намалява брояча с 1. Ако след това броячът стане отрицателен, нишката се блокира. Операцията release увеличава брояча и събужда една от чакащите нишки.

kotlin
import java.util.concurrent.Semaphore

val semaphore = Semaphore(3)

fun accessResource() {
    semaphore.acquire()
    try {
        println("${Thread.currentThread().name} работи")
    } finally {
        semaphore.release()
    }
}

Опашка за изчакване и планиране

Когато нишка извика acquire с нулев брояч, ОС я поставя в опашката FIFO на семафора. Нишката преминава в състояние BLOCKED, без да консумира процесорно време. След извикване на release, първата нишка в опашката преминава в състояние RUNNABLE и получава достъп до ресурса.

Видове семафори

В теорията на синхронизацията се различават два основни типа семафори: двоичен (binary) и броячeн (counting). Изборът на тип зависи от конкретната задача за управление на достъпа до ресурси.

Двоичен семафор (Binary Semaphore)

Двоичният семафор приема само стойности 0 и 1. По поведение наподобява mutex, но без изискване за собственост — всяка нишка може да изпълни release. Такива семафори са удобни за реализация на флагове за готовност и събития между нишки.

kotlin
val ready = Semaphore(0)

fun producer() {
    Thread.sleep(1000)
    ready.release()
}

fun consumer() {
    ready.acquire()
    println("Данните са готови")
}

Броячeн семафор (Counting Semaphore)

Броячeн семафор може да приема всяка неотрицателна стойност. Използва се за управление на пул от еднородни ресурси, където са налични няколко инстанции. Например пул от 5 мрежови връзки: всяко acquire заема една връзка, release я връща в пула.

Броячните семафори са незаменими при ограничаване на скоростта на достъп до външни услуги и реализация на пулове от нишки. Те позволяват прецизен контрол на степента на паралелизъм без ръчно управление на нишки.

ПараметърДвоичен семафорБроячeн семафор
Диапазон0 или 1от 0 до N
Нишки едновременно1до N
Приложениесигнализация, флаговепулове ресурси, rate limiting

Semaphore vs Mutex

Разработчиците често бъркат семафора и mutex, въпреки че между тях съществуват фундаментални разлики. Разбирането на тези разлики е от решаващо значение за избора на правилния механизъм за синхронизация в проекта.

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

Ключовата разлика — концепцията за собственост. Mutex винаги знае коя нишка го е завладяла и само тя може да го освободи. Семафорът няма собственик: всяка нишка може да извика release, дори без да извиква acquire. Това прави mutex по-безопасен за защита на данни, а семафора — по-гъвкав за координация.

Производителност и сценарии на употреба

На практика mutex е по-бърз за просто взаимно блокиране благодарение на оптимизациите за типичния сценарий. Семафорът изисква допълнителни разходи за поддържане на брояча. Въпреки това, за ограничаване на паралелизма или реализация на модела „производител-потребител“, семафорът е незаменим.

ХарактеристикаSemaphoreMutex
Собственостняма собственикима собственик
Освобождаваневсяка нишкасамо нишката-собственик
Броячот 0 до Nдвоичен
Употребаограничаване на паралелизма и сигнализациязащита на критична секция
Рекурсиянеда (reentrant)

Приложение на Semaphore в мобилната разработка

В разработката на мобилни приложения Semaphore се използва за управление на достъпа до ограничени ресурси: мрежови връзки, файлове, бази данни и хардуерни компоненти. Съвременните платформи предоставят удобни вградени реализации.

Пул от връзки към сървъра

Един от типичните случаи — пул от HTTP връзки. Приложението може едновременно да изпраща не повече от 4 заявки към сървъра, тъй като API на доставчика ограничава паралелизма. Семафор с начална стойност 4 гарантира, че при всяко натоварване броят на едновременните заявки няма да надхвърли лимита, а останалите нишки ще чакат на опашка.

Без семафор, при рязко увеличение на потребителската активност, сървърната инфраструктура може да претърпи внезапно претоварване, което води до тайм-аути и грешки 429 Too Many Requests. Семафорът работи като предпазител, пропускайки строго определен брой едновременни извиквания, независимо от броя на активните нишки.

Semaphore в Kotlin за Android

Android предоставя класа Semaphore от пакета java.util.concurrent. Нека разгледаме пример за ограничаване на едновременните мрежови заявки до две нишки за предотвратяване на претоварване на сървъра.

kotlin
class ApiClient {
    private val throttle = Semaphore(2)

    suspend fun fetch(url: String): Result {
        throttle.acquire()
        return try {
            httpGet(url)
        } finally {
            throttle.release()
        }
    }
}

DispatchSemaphore в Swift за iOS

В iOS DispatchSemaphore от GCD решава същата задача. Разработчиците го използват за синхронизиране на достъпа до ресурси в асинхронен код без блокиране на основната нишка.

swift
let semaphore = DispatchSemaphore(value: 3)

func processBatch(_ items: [UIImage]) {
    for img in items {
        semaphore.wait()
        DispatchQueue.global().async {
            applyFilter(to: img)
            semaphore.signal()
        }
    }
}

Типични грешки при работа със семафори

Най-честата грешка — забравен release при изключение. Ако нишка завърши с грешка преди извикване на release, семафорът завинаги остава блокиран за останалите нишки. Използвайте try/finally или defer за гарантирано освобождаване. Вторият проблем е deadlock при завладяване на няколко семафора в различен ред от различни нишки.

Модели на използване на семафори

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

Съществуват няколко доказани модела за прилагане на семафори в реални проекти. Познаването им помага да се избегнат типични грешки и да се изградят надеждни многонишкови системи.

Rate Limiter (ограничаване на скоростта)

Семафор с начална стойност N и периодичен release чрез таймер реализира ограничаване на скоростта на заявките към API. Например услугата позволява 10 заявки в секунда: семафорът стартира с 10, всяка заявка намалява брояча, а отделен TimerTask веднъж в секунда връща брояча към началната стойност. Това защитава както приложението, така и сървъра от претоварване.

Производител-Потребител чрез семафори

В класическата задача производител-потребител два семафора управляват буфера: empty (разрешения за запис) и full (разрешения за четене). Производителят извиква acquire на empty и release на full, Потребителят — обратно. Тази схема гарантира, че Потребителят никога няма да прочете празен буфер, а Производителят няма да го препълни.

Същата схема е в основата на ограничения буфер в операционните системи — цикличен буфер с фиксиран размер. В мобилни приложения този модел се използва за обработка на опашки от изображения, видео файлове и аналитични събития.

Throttling на мрежови заявки

Семафорите се използват успешно за throttling на мрежови повиквания във фонов режим. Например аналитично приложение изпраща пакети със събития към сървъра. Без ограничаване на едновременните нишки при пикови натоварвания (стартиране на приложението, синхронизация след офлайн) броят на едновременните заявки може да надхвърли лимитите на сървъра. Семафор с начална стойност 3 гарантира плавно изпращане и предотвратява блокиране от страна на сървъра.

Често задавани въпроси

Каква е разликата между Semaphore и обикновен брояч?

Семафорът не е просто брояч, а примитив за синхронизация с атомарни операции и опашка за изчакване. Обикновеният брояч не блокира нишката и не гарантира атомарност на увеличението при конкурентен достъп от множество нишки.

Може ли семафорът да причини deadlock?

Да, deadlock е възможен при завладяване на няколко семафора в различен ред от различни нишки. Например нишка A завладява S1, след това S2, а нишка B — S2, след това S1. Определете единен ред на завладяване за всички семафори в проекта.

Какво се случва при acquire с нулев брояч?

Нишката се блокира и преминава в състояние на изчакване. Тя не консумира процесорно време, докато друга нишка не извика release. В Java това е състояние BLOCKED, в Swift нишката се спира от GCD.

С какво Binary Semaphore се различава принципно от Mutex?

Основната разлика — собственост. Mutex може да бъде освободен само от нишката-собственик. Binary Semaphore може да бъде освободен от всяка нишка, което е удобно за сигнализация между нишки, но по-малко безопасно за защита на целостта на данните.

Каква начална стойност да избера за брояча?

Началната стойност зависи от сценария. За защита на един ресурс — 1. За пул от N връзки — N. За сигнализация между нишки използвайте 0, така че нишката-потребител да чака сигнал от производителя.

Заключение

  • Semaphore — примитив за синхронизация, базиран на брояч, предложен от Дайкстра през 1965 г.
  • Двоичен семафор приема стойности 0 и 1, броячният семафор — всяка неотрицателна стойност.
  • Операциите acquire и release са атомарни и нишково безопасни.
  • За разлика от mutex, семафорът не е обвързан с нишка-собственик.
  • Броячните семафори се използват за управление на пулове от ресурси и ограничаване на паралелизма.
  • Забравен release — най-честата грешка, водеща до замръзване на нишки.

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също