Semaphore: ce este, principiul de funcționare și aplicarea în sincronizarea thread-urilor

Autor: IT Sectr Publicat: 2026-03-19 Timp de citire: 8 min

Semaphore este un primitiv de sincronizare care gestionează accesul la o resursă partajată printr-un contor și o coadă de thread-uri în așteptare. Conform Wikipedia, 2024, semaforul a fost propus de Edsger Dijkstra în 1965 pentru rezolvarea problemelor de interacțiune multi-thread. Instrumentul permite limitarea numărului de thread-uri care lucrează simultan cu secțiunea critică.

Principalele idei

  • Semaphore — primitiv de sincronizare care gestionează accesul printr-un contor de permisiuni.
  • Semaforul binar ia valorile 0 și 1, funcționând ca un steag de blocare.
  • Semaforul numeric permite accesul simultan unui număr specificat de thread-uri.
  • Spre deosebire de mutex, semaforul nu este legat de un thread-proprietar.
  • Deadlock — unul dintre principalele pericole la utilizarea incorectă a semafoarelor.

Ce este Semaphore?

Semaphore — este un primitiv de sincronizare care utilizează un contor pentru a gestiona accesul la o resursă partajată. Conceptul a fost propus de Edsger Dijkstra în 1965 și a devenit fundamentul tuturor mecanismelor moderne de sincronizare în sistemele de operare.

Definiție și scop

Semaforul este o variabilă întreagă cu două operații atomice: wait (acquire) și signal (release). Operația wait micșorează contorul, iar signal îl mărește. Când contorul ajunge la zero, thread-ul care apelează wait este blocat până la executarea signal de către un alt thread.

Scopul principal al semaforului este protejarea secțiunilor critice împotriva accesului simultan al mai multor thread-uri. Spre deosebire de mutex, semaforul nu necesită legarea de thread-ul proprietar, ceea ce îl face potrivit pentru o gamă mai largă de sarcini de coordonare.

Istorie și bază teoretică

Conceptul de semafor a apărut în contextul sistemului de operare THE, dezvoltat la Technische Hogeschool Eindhoven. Dijkstra a formalizat semaforul ca o abstractizare matematică, demonstrând suficiența sa pentru implementarea oricăror primitive de sincronizare.

Cum funcționează semaforul?

Mecanismul semaforului se bazează pe două operații atomice și o coadă internă de așteptare. La apelarea acquire, thread-ul verifică valoarea contorului și fie continuă execuția, fie se blochează până la eliberarea resursei.

Contorul și operațiile atomice

La crearea semaforului se stabilește valoarea inițială a contorului de permisiuni. Fiecare apel acquire micșorează contorul cu 1. Dacă după aceasta contorul devine negativ, thread-ul se blochează. Operația release mărește contorul și trezește unul dintre thread-urile în așteptare.

kotlin
import java.util.concurrent.Semaphore

val semaphore = Semaphore(3)

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

Coada de așteptare și planificarea

Când thread-ul apelează acquire cu contorul zero, SO îl plasează în coada FIFO a semaforului. Thread-ul trece în starea BLOCKED, neconsumând timp de procesor. După apelul release, primul thread din coadă trece în starea RUNNABLE și obține acces la resursă.

Tipuri de semafoare

În teoria sincronizării se disting două tipuri principale de semafoare: binar (binary) și numeric (counting). Alegerea tipului depinde de sarcina specifică de gestionare a accesului la resurse.

Semaforul binar (Binary Semaphore)

Semaforul binar ia doar valorile 0 și 1. Prin comportament seamănă cu mutex, dar fără cerința de proprietate — orice thread poate executa release. Astfel de semafoare sunt convenabile pentru implementarea steagurilor de pregătire și a evenimentelor între thread-uri.

kotlin
val ready = Semaphore(0)

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

fun consumer() {
    ready.acquire()
    println("Datele sunt gata")
}

Semaforul numeric (Counting Semaphore)

Semaforul numeric poate lua orice valoare nenegativă. Este utilizat pentru gestionarea unui pool de resurse omogene, unde sunt disponibile mai multe instanțe. De exemplu, un pool de 5 conexiuni de rețea: fiecare acquire ocupă o conexiune, release o returnează în pool.

Semafoarele numerice sunt indispensabile pentru limitarea vitezei de acces la servicii externe și implementarea pool-urilor de thread-uri. Ele permit controlul precis al gradului de paralelism fără gestionarea manuală a thread-urilor.

ParametruSemafor binarSemafor numeric
Interval0 sau 1de la 0 la N
Thread-uri simultan1până la N
Aplicaresemnalizare, steaguripool-uri de resurse, rate limiting

Semaphore vs Mutex

Dezvoltătorii confundă adesea semaforul cu mutex-ul, deși există diferențe fundamentale între ele. Înțelegerea acestor diferențe este esențială pentru alegerea mecanismului corect de sincronizare în proiect.

Principiul de proprietate

Diferența cheie — conceptul de proprietate. Mutex știe întotdeauna care thread l-a capturat și doar acel thread îl poate elibera. Semaforul nu are proprietar: orice thread poate apela release, chiar fără a apela acquire. Acest lucru face mutex-ul mai sigur pentru protejarea datelor, iar semaforul — mai flexibil pentru coordonare.

Performanță și scenarii de utilizare

În practică, mutex este mai rapid pentru blocarea mutuală simplă datorită optimizărilor pentru scenariul tipic. Semaforul necesită cheltuieli suplimentare pentru menținerea contorului. Cu toate acestea, pentru limitarea paralelismului sau implementarea modelului „producător-consumator“, semaforul este de neînlocuit.

CaracteristicăSemaphoreMutex
Proprietatefără proprietarare proprietar
Eliberareorice threaddoar thread-ul proprietar
Contorde la 0 la Nbinar
Utilizarelimitarea paralelismului și semnalizareprotejarea secțiunii critice
Recursivitatenuda (reentrant)

Aplicarea Semaphore în dezvoltarea mobilă

În dezvoltarea aplicațiilor mobile, Semaphore este utilizat pentru gestionarea accesului la resurse limitate: conexiuni de rețea, fișiere, baze de date și componente hardware. Platformele moderne oferă implementări încorporate convenabile.

Pool de conexiuni la server

Unul dintre cazurile tipice — pool de conexiuni HTTP. Aplicația poate trimite simultan cel mult 4 cereri către server, deoarece API-ul furnizorului limitează paralelismul. Semaforul cu valoarea inițială 4 garantează că la orice sarcină numărul de cereri simultane nu va depăși limita, iar celelalte thread-uri vor aștepta în coadă.

Fără semafor, la creșterea bruscă a activității utilizatorilor, infrastructura serverului poate suferi o suprasarcină subită, ceea ce duce la timeout-uri și erori 429 Too Many Requests. Semaforul funcționează ca o siguranță, permițând strict un număr specificat de apeluri simultane, indiferent de numărul de thread-uri active.

Semaphore în Kotlin pentru Android

Android oferă clasa Semaphore din pachetul java.util.concurrent. Să examinăm un exemplu de limitare a cererilor de rețea simultane la două thread-uri pentru prevenirea suprasarcinii serverului.

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

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

DispatchSemaphore în Swift pentru iOS

În iOS, DispatchSemaphore din GCD rezolvă aceeași sarcină. Dezvoltătorii îl folosesc pentru sincronizarea accesului la resurse în cod asincron fără a bloca thread-ul principal.

swift
let semaphore = DispatchSemaphore(value: 3)

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

Erori tipice la lucrul cu semafoare

Cea mai frecventă eroare — release uitat la excepție. Dacă thread-ul se termină cu eroare înainte de a apela release, semaforul rămâne blocat pentru totdeauna pentru celelalte thread-uri. Folosiți try/finally sau defer pentru eliberare garantată. A doua problemă — deadlock la capturarea mai multor semafoare în ordine diferită de thread-uri diferite.

Pattern-uri de utilizare a semafoarelor

Semafoarele sunt aplicate nu doar pentru protejarea datelor, ci și pentru coordonarea thread-urilor în scenarii complexe multi-thread. Cunoașterea pattern-urilor comune accelerează dezvoltarea și reduce probabilitatea erorilor de sincronizare.

Există câteva pattern-uri dovedite de aplicare a semafoarelor în proiecte reale. Cunoașterea lor ajută la evitarea erorilor tipice și construirea sistemelor multi-thread fiabile.

Rate Limiter (limitarea vitezei)

Semaforul cu valoarea inițială N și release periodic printr-un timer implementează limitarea vitezei cererilor către API. De exemplu, serviciul permite 10 cereri pe secundă: semaforul pornește cu 10, fiecare cerere micșorează contorul, iar un TimerTask separat readuce contorul la valoarea inițială o dată pe secundă. Aceasta protejează atât aplicația, cât și serverul de suprasarcină.

Producător-Consumator prin semafoare

În problema clasică producător-consumator, două semafoare gestionează buffer-ul: empty (permisiuni de scriere) și full (permisiuni de citire). Producătorul apelează acquire pe empty și release pe full, Consumatorul — invers. Această schemă garantează că Consumatorul nu va citi niciodată un buffer gol, iar Producătorul nu îl va umple excesiv.

Aceeași schemă stă la baza buffer-ului delimitat în sistemele de operare — buffer circular cu dimensiune fixă. În aplicațiile mobile, pattern-ul este utilizat pentru procesarea cozilor de imagini, fișiere video și evenimente analitice.

Throttling-ul cererilor de rețea

Semafoarele sunt folosite cu succes pentru throttling-ul apelurilor de rețea în serviciile de fundal. De exemplu, o aplicație de analitică trimite pachete de evenimente la server. Fără limitarea thread-urilor concurente, la sarcinile de vârf (pornirea aplicației, sincronizarea după offline) numărul de cereri simultane poate depăși limitele serverului. Semaforul cu valoarea inițială 3 garantează trimiterea lină și previne blocarea pe partea serverului.

Întrebări frecvente

Care este diferența dintre Semaphore și un contor obișnuit?

Semaforul nu este doar un contor, ci un primitiv de sincronizare cu operații atomice și coadă de așteptare. Contorul obișnuit nu blochează thread-ul și nu garantează atomicitatea incrementării la accesul concurent al mai multor thread-uri.

Poate semaforul să cauzeze deadlock?

Da, deadlock este posibil la capturarea mai multor semafoare în ordine diferită de thread-uri diferite. De exemplu, thread-ul A capturează S1, apoi S2, iar thread-ul B — S2, apoi S1. Stabiliți o ordine unică de capturare pentru toate semafoarele din proiect.

Ce se întâmplă la acquire cu contorul zero?

Thread-ul se blochează și trece în stare de așteptare. Nu consumă timp de procesor până când un alt thread apelează release. În Java aceasta este starea BLOCKED, în Swift thread-ul este suspendat de GCD.

Cu ce se diferențiază fundamental Binary Semaphore de Mutex?

Diferența principală — proprietatea. Mutex poate fi eliberat doar de thread-ul proprietar. Binary Semaphore poate fi eliberat de orice thread, ceea ce este convenabil pentru semnalizarea între thread-uri, dar mai puțin sigur pentru protejarea integrității datelor.

Ce valoare inițială să alegem pentru contor?

Valoarea inițială depinde de scenariu. Pentru protejarea unei singure resurse — 1. Pentru un pool de N conexiuni — N. Pentru semnalizarea între thread-uri folosiți 0, astfel încât thread-ul consumator să aștepte semnalul de la producător.

Rezumat

  • Semaphore — primitiv de sincronizare bazat pe contor, propus de Dijkstra în 1965.
  • Semaforul binar ia valorile 0 și 1, cel numeric — orice valoare nenegativă.
  • Operațiile acquire și release sunt atomice și thread-sigure.
  • Spre deosebire de mutex, semaforul nu este legat de thread-ul proprietar.
  • Semafoarele numerice sunt utilizate pentru gestionarea pool-urilor de resurse și limitarea paralelismului.
  • Release uitat — cea mai frecventă eroare care duce la blocarea thread-urilor.

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și