Ang Semaphore ay isang synchronization primitive na namamahala ng access sa shared resource sa pamamagitan ng counter at queue ng mga naghihintay na thread. Ayon sa Wikipedia, 2024, ang semaphore ay iminungkahi ni Edsger Dijkstra noong 1965 para malutas ang mga problema ng multithread interaction. Ang tool ay nagpapahintulot na limitahan ang bilang ng mga thread na sabay na gumagana sa critical section.
Mga Pangunahing Punto
Semaphore — ay isang synchronization primitive na gumagamit ng counter para pamahalaan ang access sa shared resource. Ang konsepto ay iminungkahi ni Edsger Dijkstra noong 1965 at naging pundasyon ng lahat ng modernong synchronization mechanism sa operating system.
Ang semaphore ay isang integer variable na may dalawang atomic operations: wait (acquire) at signal (release). Ang wait operation ay nagpapababa ng counter, at ang signal ay nagpapataas nito. Kapag ang counter ay umabot sa zero, ang thread na tumatawag ng wait ay blocked hanggang sa maisagawa ang signal ng ibang thread.
Ang pangunahing layunin ng semaphore ay protektahan ang critical sections mula sa sabay na access ng maraming thread. Hindi tulad ng mutex, ang semaphore ay hindi nangangailangan ng pagkakatali sa may-ari na thread, na ginagawa itong angkop para sa mas malawak na hanay ng coordination tasks.
Ang konsepto ng semaphore ay lumitaw sa konteksto ng operating system na THE, na binuo sa Technische Hogeschool Eindhoven. Pormalisado ni Dijkstra ang semaphore bilang isang mathematical abstraction, na pinatunayan ang sapat nito para sa pagpapatupad ng anumang synchronization primitives.
Ang mekanismo ng semaphore ay batay sa dalawang atomic operations at isang internal waiting queue. Kapag tumatawag ng acquire, sinusuri ng thread ang halaga ng counter at maaaring magpatuloy sa pag-execute o ma-block hanggang sa ma-release ang resource.
Kapag gumagawa ng semaphore, ang initial value ng permission counter ay itinakda. Bawat pagtawag ng acquire ay nagpapababa ng counter ng 1. Kung pagkatapos nito ang counter ay maging negatibo, ang thread ay ma-block. Ang release operation ay nagpapataas ng counter at gumigising ng isa sa mga naghihintay na thread.
import java.util.concurrent.Semaphore
val semaphore = Semaphore(3)
fun accessResource() {
semaphore.acquire()
try {
println("${Thread.currentThread().name} gumagana")
} finally {
semaphore.release()
}
}
Kapag ang thread ay tumatawag ng acquire na may counter na zero, inilalagay ito ng OS sa FIFO queue ng semaphore. Ang thread ay napupunta sa BLOCKED state, hindi gumagamit ng processor time. Pagkatapos ng release call, ang unang thread sa queue ay napupunta sa RUNNABLE state at nakakakuha ng access sa resource.
Sa synchronization theory, dalawang pangunahing uri ng semaphore ang nakikilala: binary at counting. Ang pagpili ng uri ay depende sa partikular na gawain ng pamamahala ng access sa resources.
Ang binary semaphore ay kumukuha lamang ng mga halaga 0 at 1. Sa pag-uugali, ito ay kahawig ng mutex, ngunit walang kinakailangan ng pagmamay-ari — anumang thread ay maaaring mag-execute ng release. Ang ganitong mga semaphore ay maginhawa para sa pagpapatupad ng mga readiness flag at inter-thread na mga event.
val ready = Semaphore(0)
fun producer() {
Thread.sleep(1000)
ready.release()
}
fun consumer() {
ready.acquire()
println("Ang data ay handa")
}
Counting semaphore ay maaaring kumuha ng anumang non-negative na halaga. Ito ay ginagamit para sa pamamahala ng pool ng mga homogenous na resource kung saan maraming instance ang available. Halimbawa, isang pool ng 5 network connections: bawat acquire ay kumukuha ng isang connection, ang release ay nagbabalik nito sa pool.
Ang counting semaphores ay kailangang-kailangan sa paglilimita ng bilis ng access sa external services at pagpapatupad ng thread pools. Pinapayagan nila ang tumpak na kontrol ng antas ng parallelism nang walang manual na pamamahala ng thread.
| Parameter | Binary Semaphore | Counting Semaphore |
|---|---|---|
| Saklaw | 0 o 1 | mula 0 hanggang N |
| Sabay na thread | 1 | hanggang N |
| Aplikasyon | signaling, mga flag | resource pools, rate limiting |
Ang mga developer ay madalas na nagkakamali ng semaphore at mutex, kahit na may mga pundamental na pagkakaiba sa pagitan nila. Ang pag-unawa sa mga pagkakaibang ito ay mahalaga para sa pagpili ng tamang synchronization mechanism sa proyekto.
Ang pangunahing pagkakaiba — konsepto ng pagmamay-ari. Ang mutex ay laging alam kung aling thread ang kumuha nito, at tanging ang thread na iyon ang makakapag-release nito. Ang semaphore ay walang may-ari: anumang thread ay maaaring tumawag ng release, kahit na hindi tumatawag ng acquire. Ginagawa nitong mas ligtas ang mutex para sa proteksyon ng data, at ang semaphore — mas flexible para sa koordinasyon.
Sa praktika, ang mutex ay mas mabilis para sa simpleng mutual locking dahil sa mga optimization para sa tipikal na sitwasyon. Ang semaphore ay nangangailangan ng karagdagang overhead para sa pagpapanatili ng counter. Gayunpaman, para sa paglilimita ng parallelism o pagpapatupad ng pattern na “producer-consumer”, ang semaphore ay kailangang-kailangan.
| Katangian | Semaphore | Mutex |
|---|---|---|
| Pagmamay-ari | walang may-ari | may may-ari |
| Pag-release | anumang thread | tanging may-ari na thread |
| Counter | mula 0 hanggang N | binary |
| Use case | paglilimita ng parallelism at signaling | proteksyon ng critical section |
| Recursion | hindi | oo (reentrant) |
Sa pag-develop ng mobile applications, ang Semaphore ay ginagamit para sa pamamahala ng access sa limitadong resources: network connections, files, databases, at hardware components. Ang mga modernong platform ay nagbibigay ng mga maginhawang built-in na implementasyon.
Isa sa mga tipikal na kaso — pool ng HTTP connections. Ang aplikasyon ay maaaring magpadala ng hindi hihigit sa 4 na sabay na request sa server, dahil ang API ng provider ay naglilimita ng parallelism. Ang semaphore na may initial value na 4 ay ginagarantiyahan na sa anumang load, ang bilang ng sabay na request ay hindi lalampas sa limit, at ang ibang threads ay maghihintay sa queue.
Kung walang semaphore, sa biglaang pagtaas ng aktibidad ng user, ang server infrastructure ay maaaring makaranas ng biglaang overload, na humahantong sa timeouts at errors 429 Too Many Requests. Ang semaphore ay gumagana tulad ng isang fuse, na nagpapahintulot ng mahigpit na tinukoy na bilang ng sabay na tawag anuman ang bilang ng aktibong threads.
Ang Android ay nagbibigay ng class na Semaphore mula sa package na java.util.concurrent. Tingnan natin ang halimbawa ng paglilimita ng sabay na network requests sa dalawang thread upang maiwasan ang server overload.
class ApiClient {
private val throttle = Semaphore(2)
suspend fun fetch(url: String): Result {
throttle.acquire()
return try {
httpGet(url)
} finally {
throttle.release()
}
}
}
Sa iOS, ang DispatchSemaphore mula sa GCD ay nilulutas ang parehong gawain. Ginagamit ito ng mga developer para i-sync ang access sa resources sa asynchronous code nang hindi bina-block ang main thread.
let semaphore = DispatchSemaphore(value: 3)
func processBatch(_ items: [UIImage]) {
for img in items {
semaphore.wait()
DispatchQueue.global().async {
applyFilter(to: img)
semaphore.signal()
}
}
}
Ang pinakakaraniwang pagkakamali — nakalimutang release sa exception. Kung ang thread ay natapos na may error bago tumawag ng release, ang semaphore ay mananatiling blocked magpakailanman para sa ibang threads. Gamitin ang try/finally o defer para sa garantisadong pag-release. Ang pangalawang problema ay deadlock kapag kumukuha ng maraming semaphore sa magkaibang pagkakasunod-sunod ng iba't ibang threads.
Ang semaphores ay ginagamit hindi lamang para sa proteksyon ng data, kundi pati na rin para sa koordinasyon ng threads sa mga kumplikadong multithread na sitwasyon. Ang kaalaman sa mga karaniwang pattern ay nagpapabilis ng development at nagpapababa ng posibilidad ng synchronization errors.
Mayroong ilang mga napatunayang pattern ng paggamit ng semaphore sa mga totoong proyekto. Ang pag-alam sa mga ito ay tumutulong na maiwasan ang mga tipikal na pagkakamali at bumuo ng maaasahang multithread system.
Ang semaphore na may initial value N at periodic release sa pamamagitan ng timer ay nagpapatupad ng paglilimita ng bilis ng mga request sa API. Halimbawa, ang serbisyo ay nagpapahintulot ng 10 request bawat segundo: ang semaphore ay nagsisimula sa 10, bawat request ay nagpapababa ng counter, at isang hiwalay na TimerTask ay nagbabalik ng counter sa initial value isang beses bawat segundo. Pinoprotektahan nito ang aplikasyon at server mula sa overload.
Sa klasikong producer-consumer task, dalawang semaphore ang namamahala ng buffer: empty (write permissions) at full (read permissions). Ang Producer ay tumatawag ng acquire sa empty at release sa full, ang Consumer — kabaligtaran. Ginagarantiyahan ng scheme na ito na hindi kailanman babasahin ng Consumer ang empty buffer, at hindi ito mapupuno nang sobra ng Producer.
Ang parehong scheme ay nasa puso ng bounded buffer sa operating systems — ang circular buffer na may fixed size. Sa mobile applications, ang pattern ay ginagamit para sa pagproseso ng mga queue ng images, video files, at analytic events.
Ang semaphores ay matagumpay na ginagamit para sa throttling ng network calls sa background services. Halimbawa, isang analytics application ay nagpapadala ng mga event packet sa server. Kung walang paglilimita ng concurrent threads, sa peak loads (pagsisimula ng application, synchronization pagkatapos ng offline) ang bilang ng sabay na request ay maaaring lumampas sa server limits. Ang semaphore na may initial value na 3 ay ginagarantiyahan ang maayos na pagpapadala at pumipigil sa pag-block sa server side.
Mga Madalas Itanong
Ang semaphore ay hindi lamang isang counter, kundi isang synchronization primitive na may atomic operations at waiting queue. Ang ordinaryong counter ay hindi bina-block ang thread at hindi ginagarantiyahan ang atomicity ng incrementation sa concurrent access ng maraming threads.
Oo, ang deadlock ay posible kapag kumukuha ng maraming semaphore sa magkaibang pagkakasunod-sunod ng iba't ibang threads. Halimbawa, thread A ay kumukuha ng S1, pagkatapos S2, at thread B — S2, pagkatapos S1. Magtakda ng uniform na pagkakasunod-sunod ng pagkuha para sa lahat ng semaphore sa proyekto.
Ang thread ay mababa-block at mapupunta sa waiting state. Hindi ito gumagamit ng processor time hanggang sa ibang thread ay tumawag ng release. Sa Java ito ay BLOCKED state, sa Swift ang thread ay sinuspinde ng GCD.
Ang pangunahing pagkakaiba — pagmamay-ari. Ang Mutex ay maaari lamang i-release ng may-ari na thread. Ang Binary Semaphore ay maaaring i-release ng anumang thread, na maginhawa para sa inter-thread signaling, ngunit hindi gaanong ligtas para sa proteksyon ng data integrity.
Ang initial value ay depende sa sitwasyon. Para sa proteksyon ng isang resource — 1. Para sa pool ng N connections — N. Para sa inter-thread signaling ay gumamit ng 0, upang ang consumer thread ay maghintay ng signal mula sa producer.
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din