Semaphore: mi ez, működési elv és alkalmazása szálak szinkronizálásában

Szerző: IT Sectr Megjelenés: 2026-03-19 Olvasási idő: 8 perc

A Semaphore egy szinkronizációs primitív, amely egy számlálón és egy várakozó szálakból álló soron keresztül vezérli a hozzáférést egy megosztott erőforráshoz. A Wikipedia, 2024 szerint a szemaforot Edsger Dijkstra javasolta 1965-ben a többszálú interakció problémáinak megoldására. Az eszköz lehetővé teszi a kritikus szekcióval egyidejűleg dolgozó szálak számának korlátozását.

Főbb pontok

  • Semaphore — szinkronizációs primitív, amely egy engedélyszámlálón keresztül vezérli a hozzáférést.
  • Bináris szemafor 0 és 1 értékeket vesz fel, zárolási flag-ként működik.
  • Számláló szemafor lehetővé teszi meghatározott számú szál egyidejű hozzáférését.
  • A mutex-től eltérően a szemafor nem kötődik a tulajdonos szálhoz.
  • Deadlock — az egyik fő veszély a szemaforok helytelen használatakor.

Mi az a Semaphore?

Semaphore — egy szinkronizációs primitív, amely egy számlálót használ a megosztott erőforráshoz való hozzáférés vezérlésére. A koncepciót Edsger Dijkstra javasolta 1965-ben, és az összes modern szinkronizációs mechanizmus alapjává vált az operációs rendszerekben.

Definíció és cél

A szemafor egy egész típusú változó két atomi művelettel: wait (acquire) és signal (release). A wait művelet csökkenti a számlálót, a signal pedig növeli. Amikor a számláló eléri a nullát, a wait-et meghívó szál blokkolódik, amíg egy másik szál végre nem hajtja a signal-t.

A szemafor fő célja a kritikus szekciók védelme több szál egyidejű hozzáférésétől. A mutex-től eltérően a szemafor nem igényel kötődést a tulajdonos szálhoz, ami szélesebb körű koordinációs feladatokra teszi alkalmassá.

Történet és elméleti alap

A szemafor koncepciója a THE operációs rendszer kontextusában merült fel, amelyet a Technische Hogeschool Eindhoven-ben fejlesztettek ki. Dijkstra matematikai absztrakcióként formalizálta a szemaforot, bizonyítva annak elegendőségét bármely szinkronizációs primitív megvalósításához.

Hogyan működik a szemafor?

A szemafor mechanizmusa két atomi műveleten és egy belső várakozási soron alapul. Az acquire hívásakor a szál ellenőrzi a számláló értékét, és vagy folytatja a végrehajtást, vagy blokkolódik az erőforrás felszabadulásáig.

Számláló és atomi műveletek

A szemafor létrehozásakor beállításra kerül az engedélyszámláló kezdeti értéke. Minden acquire hívás 1-gyel csökkenti a számlálót. Ha ezután a számláló negatívvá válik, a szál blokkolódik. A release művelet növeli a számlálót és felébreszti az egyik várakozó szálat.

kotlin
import java.util.concurrent.Semaphore

val semaphore = Semaphore(3)

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

Várakozási sor és ütemezés

Amikor egy szál nulla számlálóval hívja meg az acquire-t, az OS elhelyezi a szemafor FIFO sorába. A szál BLOCKED állapotba kerül, nem fogyaszt processzoridőt. A release hívás után a sor első szálja RUNNABLE állapotba kerül és hozzáférést kap az erőforráshoz.

A szemaforok típusai

A szinkronizációs elméletben a szemaforok két fő típusát különböztetjük meg: bináris (binary) és számláló (counting). A típus kiválasztása az erőforrásokhoz való hozzáférés kezelésének konkrét feladatától függ.

Bináris szemafor (Binary Semaphore)

A bináris szemafor csak 0 és 1 értékeket vesz fel. Viselkedésében a mutex-re hasonlít, de tulajdonjogi követelmény nélkül — bármely szál végrehajthatja a release-t. Az ilyen szemaforok alkalmasak készenléti flag-ek és szálak közötti események megvalósítására.

kotlin
val ready = Semaphore(0)

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

fun consumer() {
    ready.acquire()
    println("Az adatok készen állnak")
}

Számláló szemafor (Counting Semaphore)

Számláló szemafor bármilyen nem negatív értéket felvehet. Homogén erőforrások pool-jának kezelésére használják, ahol több példány áll rendelkezésre. Például egy 5 hálózati kapcsolatból álló pool: minden acquire elfoglal egy kapcsolatot, a release visszahelyezi a pool-ba.

Számláló szemaforok nélkülözhetetlenek a külső szolgáltatásokhoz való hozzáférés sebességének korlátozásában és a szálpool-ok megvalósításában. Lehetővé teszik a párhuzamosság fokának pontos szabályozását a szálak kézi kezelése nélkül.

ParaméterBináris szemaforSzámláló szemafor
Tartomány0 vagy 10-tól N-ig
Egyidejű szálak1N-ig
Alkalmazásjelzés, flag-ekerőforrás pool-ok, rate limiting

Semaphore vs Mutex

A fejlesztők gyakran összekeverik a szemafort és a mutex-t, bár alapvető különbségek vannak közöttük. E különbségek megértése kritikus fontosságú a megfelelő szinkronizációs mechanizmus kiválasztásához a projektben.

Tulajdonjog elve

A kulcsfontosságú különbség — a tulajdonjog fogalma. A mutex mindig tudja, melyik szál foglalta le, és csak az a szál szabadíthatja fel. A szemafornak nincs tulajdonosa: bármely szál meghívhatja a release-t, akár az acquire meghívása nélkül is. Ez a mutex-t biztonságosabbá teszi az adatvédelemhez, a szemafor-t pedig rugalmasabbá a koordinációhoz.

Teljesítmény és használati forgatókönyvek

A gyakorlatban a mutex gyorsabb az egyszerű kölcsönös zároláshoz a tipikus forgatókönyv optimalizálásainak köszönhetően. A szemafor további overhead-et igényel a számláló fenntartásához. Mindazonáltal a párhuzamosság korlátozásához vagy a “termelő-fogyasztó” minta megvalósításához a szemafor nélkülözhetetlen.

JellemzőSemaphoreMutex
Tulajdonjognincs tulajdonosvan tulajdonos
Felszabadításbármely szálcsak tulajdonos szál
Számláló0-tól N-igbináris
Use casepárhuzamosság korlátozása és jelzéskritikus szekció védelme
Rekurziónemigen (reentrant)

A Semaphore alkalmazása mobil fejlesztésben

A mobilalkalmazások fejlesztésében a Semaphore a korlátozott erőforrásokhoz való hozzáférés kezelésére szolgál: hálózati kapcsolatok, fájlok, adatbázisok és hardverkomponensek. A modern platformok kényelmes beépített megvalósításokat kínálnak.

Kiszolgáló kapcsolatainak pool-ja

Az egyik tipikus eset — HTTP kapcsolatok pool-ja. Az alkalmazás egyszerre legfeljebb 4 kérést küldhet a kiszolgálónak, mivel a szolgáltató API-ja korlátozza a párhuzamosságot. A 4-es kezdeti értékű szemafor garantálja, hogy bármilyen terhelés mellett az egyidejű kérések száma nem lépi túl a határt, a többi szál pedig a sorban várakozik.

Szemafor nélkül a felhasználói aktivitás hirtelen növekedésekor a kiszolgáló infrastruktúra hirtelen túlterhelődhet, ami időtúllépésekhez és 429 Too Many Requests hibákhoz vezet. A szemafor biztosítékként működik, szigorúan meghatározott számú egyidejű hívást engedélyezve az aktív szálak számától függetlenül.

Semaphore Kotlin-ban Android-hoz

Az Android a java.util.concurrent csomagból biztosítja a Semaphore osztályt. Vizsgáljuk meg az egyidejű hálózati kérések két szálra korlátozásának példáját a kiszolgáló túlterhelésének megelőzése érdekében.

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-ben iOS-hez

iOS-ben a GCD DispatchSemaphore-ja ugyanezt a feladatot oldja meg. A fejlesztők az aszinkron kódban lévő erőforrásokhoz való hozzáférés szinkronizálására használják a főszál blokkolása nélkül.

swift
let semaphore = DispatchSemaphore(value: 3)

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

Tipikus hibák a szemaforokkal való munkában

A leggyakoribb hiba — az elfelejtett release kivétel esetén. Ha egy szál hiba miatt a release meghívása előtt fejeződik be, a szemafor örökre blokkolva marad a többi szál számára. Használjon try/finally vagy defer-t a garantált felszabadításhoz. A második probléma a deadlock, amikor több szemafor különböző sorrendben történő lefoglalása különböző szálak által történik.

Szemaforok használati mintái

A szemaforokat nem csak adatvédelemre, hanem a szálak koordinálására is használják összetett többszálú forgatókönyvekben. A gyakori minták ismerete felgyorsítja a fejlesztést és csökkenti a szinkronizációs hibák valószínűségét.

Több bevált minta létezik a szemaforok valós projektekben történő alkalmazására. Ezek ismerete segít elkerülni a tipikus hibákat és megbízható többszálú rendszereket építeni.

Rate Limiter (sebességkorlátozó)

Az N kezdeti értékű szemafor és egy timeren keresztüli periodikus release megvalósítja az API hívások sebességkorlátozását. Például a szolgáltatás 10 kérést engedélyez másodpercenként: a szemafor 10-zel indul, minden kérés csökkenti a számlálót, és egy külön TimerTask másodpercenként visszaállítja a számlálót a kezdeti értékre. Ez védi az alkalmazást és a kiszolgálót a túlterheléstől.

Termelő-Fogyasztó szemaforokon keresztül

A klasszikus termelő-fogyasztó feladatban két szemafor kezeli a puffert: empty (írási engedélyek) és full (olvasási engedélyek). A termelő acquire-t hív az empty-n és release-t a full-on, a fogyasztó — fordítva. Ez a séma garantálja, hogy a fogyasztó soha nem olvas üres puffert, és a termelő nem tölti túl azt.

Ugyanez a séma áll a korlátozott puffer mögött az operációs rendszerekben — a körpuffer fix mérettel. Mobil alkalmazásokban ezt a mintát képek, videofájlok és analitikai események sorainak feldolgozására használják.

Hálózati kérések throttling-je

A szemaforokat sikeresen használják a hálózati hívások throttling-jére háttérszolgáltatásokban. Például egy analitikai alkalmazás eseménycsomagokat küld a kiszolgálónak. A párhuzamos szálak korlátozása nélkül csúcsterheléskor (alkalmazás indítása, offline utáni szinkronizálás) az egyidejű kérések száma meghaladhatja a kiszolgáló korlátait. A 3-as kezdeti értékű szemafor garantálja a zökkenőmentes küldést és megakadályozza a blokkolást a kiszolgáló oldalán.

Gyakran Ismételt Kérdések

Mi a különbség a Semaphore és egy közönséges számláló között?

A szemafor nem egyszerű számláló, hanem szinkronizációs primitív atomi műveletekkel és várakozási sorral. A közönséges számláló nem blokkolja a szálat és nem garantálja az inkrementáció atomosságát több szál egyidejű hozzáférése esetén.

Okozhat-e a szemafor deadlock-ot?

Igen, a deadlock lehetséges, ha több szemafor különböző sorrendben történő lefoglalása különböző szálak által történik. Például, az A szál lefoglalja S1-et, majd S2-t, a B szál pedig S2-t, majd S1-et. Állítson fel egységes lefoglalási sorrendet a projekt összes szemaforjához.

Mi történik acquire híváskor nulla számlálóval?

A szál blokkolódik és várakozó állapotba kerül. Nem fogyaszt processzoridőt, amíg egy másik szál meg nem hívja a release-t. Java-ban ez a BLOCKED állapot, Swift-ben a szálat a GCD felfüggeszti.

Miben különbözik alapvetően a Binary Semaphore a Mutex-től?

A fő különbség — a tulajdonjog. A Mutex csak a tulajdonos szál által szabadítható fel. A Binary Semaphore bármely szál által felszabadítható, ami kényelmes a szálak közötti jelzéshez, de kevésbé biztonságos az adatok integritásának védelméhez.

Milyen kezdeti értéket válasszunk a számlálóhoz?

A kezdeti érték a forgatókönyvtől függ. Egyetlen erőforrás védelméhez — 1. N kapcsolatból álló pool-hoz — N. Szálak közötti jelzéshez használjon 0-t, hogy a fogyasztó szál várjon a termelő jelzésére.

Összefoglalás

  • Semaphore — számlálón alapuló szinkronizációs primitív, amelyet Dijkstra javasolt 1965-ben.
  • Bináris szemafor 0 és 1 értékeket vesz fel, számláló szemafor — bármilyen nem negatív értéket.
  • Az acquire és release műveletek atomiak és szálbiztosak.
  • A mutex-től eltérően a szemafor nem kötődik a tulajdonos szálhoz.
  • Számláló szemaforok erőforrás pool-ok kezelésére és párhuzamosság korlátozására szolgálnak.
  • Elfelejtett release — a leggyakoribb hiba, amely szálak befagyásához vezet.

Kulcsrakész mobilalkalmazást fejlesztünk

Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.

Projekt megbeszélése

Olvassa el is