Semaphore: co to je, princip fungování a použití v synchronizaci vláken

Autor: IT Sectr Publikováno: 2026-03-19 Doba čtení: 8 min

Semaphore je synchronizační primitivum, které řídí přístup ke sdílenému prostředku pomocí čítače a fronty čekajících vláken. Podle Wikipedia, 2024 byl semafor navržen Edsgerem Dijkstrou v roce 1965 pro řešení problémů vícevláknové interakce. Nástroj umožňuje omezit počet vláken pracujících současně s kritickou sekcí.

Hlavní body

  • Semaphore — synchronizační primitivum řídící přístup pomocí čítače povolení.
  • Binární semafor nabývá hodnot 0 a 1, funguje jako zámkový příznak.
  • Čítačový semafor umožňuje současný přístup určeného počtu vláken.
  • Na rozdíl od mutexu není semafor vázán na vlastnící vlákno.
  • Deadlock — jedno z hlavních nebezpečí při nesprávném použití semaforů.

Co je Semaphore?

Semaphore — je synchronizační primitivum, které používá čítač k řízení přístupu ke sdílenému prostředku. Koncept navrhl Edsger Dijkstra v roce 1965 a stal se základem všech moderních synchronizačních mechanismů v operačních systémech.

Definice a účel

Semafor je celočíselná proměnná se dvěma atomickými operacemi: wait (acquire) a signal (release). Operace wait snižuje čítač a signal jej zvyšuje. Když čítač dosáhne nuly, vlákno volající wait je blokováno, dokud signal neprovede jiné vlákno.

Hlavním účelem semaforu je ochrana kritických sekcí před současným přístupem více vláken. Na rozdíl od mutexu semafor nevyžaduje vázání na vlastnící vlákno, což jej činí vhodným pro širší rozsah koordinačních úloh.

Historie a teoretický základ

Koncept semaforu vznikl v kontextu operačního systému THE, vyvinutého na Technische Hogeschool Eindhoven. Dijkstra formalizoval semafor jako matematickou abstrakci a dokázal jeho dostatečnost pro implementaci libovolných synchronizačních primitiv.

Jak funguje semafor?

Mechanismus semaforu je založen na dvou atomických operacích a interní frontě čekání. Při volání acquire vlákno zkontroluje hodnotu čítače a buď pokračuje ve vykonávání, nebo je blokováno, dokud není prostředek uvolněn.

Čítač a atomické operace

Při vytvoření semaforu se nastaví počáteční hodnota čítače povolení. Každé volání acquire snižuje čítač o 1. Pokud se poté čítač stane záporným, vlákno je blokováno. Operace release zvyšuje čítač a probouzí jedno z čekajících vláken.

kotlin
import java.util.concurrent.Semaphore

val semaphore = Semaphore(3)

fun accessResource() {
    semaphore.acquire()
    try {
        println("${Thread.currentThread().name} pracuje")
    } finally {
        semaphore.release()
    }
}

Fronta čekání a plánování

Když vlákno zavolá acquire s nulovým čítačem, OS jej umístí do fronty FIFO semaforu. Vlákno přejde do stavu BLOCKED, nespotřebovává čas procesoru. Po volání release první vlákno ve frontě přejde do stavu RUNNABLE a získá přístup k prostředku.

Typy semaforů

V teorii synchronizace rozlišujeme dva hlavní typy semaforů: binární (binary) a čítačový (counting). Volba typu závisí na konkrétním úkolu řízení přístupu k prostředkům.

Binární semafor (Binary Semaphore)

Binární semafor nabývá pouze hodnot 0 a 1. Svým chováním připomíná mutex, ale bez požadavku vlastnictví — jakékoli vlákno může provést release. Takové semafory jsou vhodné pro implementaci příznaků připravenosti a událostí mezi vlákny.

kotlin
val ready = Semaphore(0)

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

fun consumer() {
    ready.acquire()
    println("Data jsou připravena")
}

Čítačový semafor (Counting Semaphore)

Čítačový semafor může nabývat libovolné nezáporné hodnoty. Používá se pro správu fondu homogenních prostředků, kde je k dispozici několik instancí. Například fond 5 síťových připojení: každé acquire zabere jedno připojení, release jej vrátí do fondu.

Čítačové semafory jsou nepostradatelné při omezování rychlosti přístupu k externím službám a implementaci fondů vláken. Umožňují přesnou kontrolu stupně paralelismu bez ruční správy vláken.

ParametrBinární semaforČítačový semafor
Rozsah0 nebo 1od 0 do N
Vláken současně1až N
Použitísignalizace, příznakyfondy prostředků, rate limiting

Semaphore vs Mutex

Vývojáři si často pletou semafor a mutex, i když mezi nimi existují zásadní rozdíly. Pochopení těchto rozdílů je klíčové pro výběr správného synchronizačního mechanismu v projektu.

Princip vlastnictví

Klíčový rozdíl — koncept vlastnictví. Mutex vždy ví, které vlákno jej zachytilo, a pouze toto vlákno jej může uvolnit. Semafor nemá vlastníka: jakékoli vlákno může zavolat release, aniž by volalo acquire. To činí mutex bezpečnějším pro ochranu dat a semafor flexibilnějším pro koordinaci.

Výkon a scénáře použití

V praxi je mutex rychlejší pro jednoduché vzájemné blokování díky optimalizacím pro typický scénář. Semafor vyžaduje dodatečnou režii na udržování čítače. Avšak pro omezení paralelismu nebo implementaci vzoru „producent-konzument“ je semafor nepostradatelný.

VlastnostSemaphoreMutex
Vlastnictvíbez vlastníkamá vlastníka
Uvolněníjakékoli vláknopouze vlastnící vlákno
Čítačod 0 do Nbinární
Use caseomezení paralelismu a signalizaceochrana kritické sekce
Rekurzeneano (reentrant)

Použití Semaphore v mobilním vývoji

Při vývoji mobilních aplikací se Semaphore používá pro řízení přístupu k omezeným prostředkům: síťovým připojením, souborům, databázím a hardwarovým komponentám. Moderní platformy poskytují pohodlné vestavěné implementace.

Fond připojení k serveru

Jedním z typických případů je fond HTTP připojení. Aplikace může současně odesílat nejvýše 4 požadavky na server, protože API poskytovatele omezuje paralelismus. Semafor s počáteční hodnotou 4 zaručuje, že při jakémkoli zatížení počet současných požadavků nepřekročí limit a ostatní vlákna budou čekat ve frontě.

Bez semaforu může při náhlém nárůstu uživatelské aktivity dojít k přetížení serverové infrastruktury, což vede k timeoutům a chybám 429 Too Many Requests. Semafor funguje jako pojistka, propouštějící striktně stanovený počet současných volání bez ohledu na počet aktivních vláken.

Semaphore v Kotlin pro Android

Android poskytuje třídu Semaphore z balíčku java.util.concurrent. Podívejme se na příklad omezení současných síťových požadavků na dvě vlákna pro zabránění přetížení serveru.

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

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

DispatchSemaphore ve Swift pro iOS

V iOS řeší DispatchSemaphore z GCD stejný úkol. Vývojáři jej používají pro synchronizaci přístupu k prostředkům v asynchronním kódu bez blokování hlavního vlákna.

swift
let semaphore = DispatchSemaphore(value: 3)

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

Typické chyby při práci se semafory

Nejčastější chybou je zapomenuté release při výjimce. Pokud vlákno skončí s chybou před voláním release, semafor zůstane navždy zablokován pro ostatní vlákna. Použijte try/finally nebo defer pro zaručené uvolnění. Druhým problémem je deadlock při zachycování několika semaforů v různém pořadí různými vlákny.

Vzory použití semaforů

Semafory se používají nejen pro ochranu dat, ale také pro koordinaci vláken ve složitých vícevláknových scénářích. Znalost běžných vzorů urychluje vývoj a snižuje pravděpodobnost synchronizačních chyb.

Existuje několik osvědčených vzorů použití semaforů v reálných projektech. Jejich znalost pomáhá vyhnout se typickým chybám a budovat spolehlivé vícevláknové systémy.

Rate Limiter (omezování rychlosti)

Semafor s počáteční hodnotou N a periodickým release pomocí časovače implementuje omezování rychlosti požadavků na API. Například služba povoluje 10 požadavků za sekundu: semafor startuje s 10, každý požadavek snižuje čítač a samostatný TimerTask jednou za sekundu vrací čítač na počáteční hodnotu. To chrání aplikaci i server před přetížením.

Producent-Konzument přes semafory

V klasické úloze producent-konzument dva semafory řídí buffer: empty (povolení k zápisu) a full (povolení ke čtení). Producent volá acquire na empty a release na full, Konzument — obráceně. Toto schéma zaručuje, že Konzument nikdy nepřečte prázdný buffer a Producent jej nepřetéká.

Stejné schéma je základem omezeného bufferu v operačních systémech — kruhového bufferu s pevnou velikostí. V mobilních aplikacích se tento vzor používá pro zpracování front obrázků, video souborů a analytických událostí.

Throttling síťových požadavků

Semafory se úspěšně používají pro throttling síťových volání v backendových službách. Například analytická aplikace odesílá pakety událostí na server. Bez omezení souběžných vláken může při špičkovém zatížení (spuštění aplikace, synchronizace po offline) počet současných požadavků překročit limity serveru. Semafor s počáteční hodnotou 3 zaručuje plynulé odesílání a zabraňuje blokování na straně serveru.

Často kladené otázky

Jaký je rozdíl mezi Semaphore a běžným čítačem?

Semafor není jen čítač, ale synchronizační primitivum s atomickými operacemi a frontou čekání. Běžný čítač neblokuje vlákno a nezaručuje atomicitu inkrementace při konkurenčním přístupu více vláken.

Může semafor způsobit deadlock?

Ano, deadlock je možný při zachycování několika semaforů v různém pořadí různými vlákny. Například vlákno A zachytí S1, poté S2, a vlákno B — S2, poté S1. Stanovte jednotné pořadí zachycování pro všechny semafory v projektu.

Co se stane při acquire s nulovým čítačem?

Vlákno je blokováno a přejde do stavu čekání. Nespotřebovává čas procesoru, dokud jiné vlákno nezavolá release. V Javě je to stav BLOCKED, ve Swift je vlákno pozastaveno GCD.

Čím se Binary Semaphore zásadně liší od Mutexu?

Hlavní rozdíl — vlastnictví. Mutex může uvolnit pouze vlastnící vlákno. Binary Semaphore může uvolnit jakékoli vlákno, což je vhodné pro signalizaci mezi vlákny, ale méně bezpečné pro ochranu integrity dat.

Jakou počáteční hodnotu zvolit pro čítač?

Počáteční hodnota závisí na scénáři. Pro ochranu jednoho prostředku — 1. Pro fond N připojení — N. Pro signalizaci mezi vlákny použijte 0, aby konzumní vlákno čekalo na signál od producenta.

Shrnutí

  • Semaphore — synchronizační primitivum založené na čítači, navržené Dijkstrou v roce 1965.
  • Binární semafor nabývá hodnot 0 a 1, čítačový — libovolné nezáporné hodnoty.
  • Operace acquire a release jsou atomické a thread-safe.
  • Na rozdíl od mutexu není semafor vázán na vlastnící vlákno.
  • Čítačové semafory se používají pro správu fondů prostředků a omezení paralelismu.
  • Zapomenuté release — nejčastější chyba vedoucí k zamrznutí vláken.

Vyvineme mobilní aplikaci na klíč

IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.

Prodiskutovat projekt

Přečtěte si také