Semaphore је примитив синхронизације који управља приступом заједничком ресурсу преко бројача и реда нити које чекају. Према Wikipedia, 2024, семафор је предложио Едсгер Дајкстра 1965. године за решавање проблема вишенитне интеракције. Алат омогућава ограничавање броја нити које истовремено раде са критичном секцијом.
Главно
Semaphore — је примитив синхронизације који користи бројач за управљање приступом заједничком ресурсу. Концепт је предложио Едсгер Дајкстра 1965. године и постао је темељ свих модерних механизама синхронизације у оперативним системима.
Семафор представља целобројну променљиву са две атомске операције: wait (acquire) и signal (release). Операција wait смањује бројач, а signal га повећава. Када бројач достигне нулу, нит која позива wait се блокира док signal не изврши друга нит.
Основна намена семафора је заштита критичних секција од истовременог приступа више нити. За разлику од мутекса, семафор не захтева везивање за нит-власника, што га чини погодним за шири круг задатака координације.
Концепт семафора настао је у контексту оперативног система THE, развијеног на Technische Hogeschool Eindhoven. Дајкстра је формализовао семафор као математичку апстракцију, доказавши његову довољност за имплементацију било којих примитива синхронизације.
Механизам семафора се заснива на две атомске операције и унутрашњем реду чекања. При позиву acquire нит проверава вредност бројача и или наставља извршење или се блокира до ослобађања ресурса.
При стварању семафора поставља се почетна вредност бројача дозвола. Сваки позив acquire смањује бројач за 1. Ако након тога бројач постане негативан, нит се блокира. Операција release повећава бројач и буди једну од нити које чекају.
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) и бројачки (counting). Избор типа зависи од конкретног задатка управљања приступом ресурсима.
Бинарни семафор узима само вредности 0 и 1. По понашању подсећа на мутекс, али без захтева власништва — било која нит може извршити release. Такви семафори су погодни за имплементацију заставица спремности и догађаја између нити.
val ready = Semaphore(0)
fun producer() {
Thread.sleep(1000)
ready.release()
}
fun consumer() {
ready.acquire()
println("Подаци су спремни")
}
Бројачки семафор може узети било коју ненегативну вредност. Користи се за управљање базеном истоврсних ресурса где је доступно неколико инстанци. На пример, базен од 5 мрежних веза: сваки acquire заузима једну везу, release је враћа у базен.
Бројачки семафори су незаменљиви при ограничавању брзине приступа спољним сервисима и имплементацији базена нити. Они омогућавају прецизну контролу степена паралелизма без ручног управљања нитима.
| Параметар | Бинарни семафор | Бројачки семафор |
|---|---|---|
| Опсег | 0 или 1 | од 0 до N |
| Нити истовремено | 1 | до N |
| Примена | сигнализација, заставице | базени ресурса, rate limiting |
Програмери често мешају семафор и мутекс, иако постоје фундаменталне разлике између њих. Разумевање ових разлика је кључно за избор исправног механизма синхронизације у пројекту.
Кључна разлика — концепт власништва. Мутекс увек зна која нит га је заузела и само та нит га може ослободити. Семафор нема власника: било која нит може позвати release, чак и без позива acquire. То чини мутекс сигурнијим за заштиту података, а семафор — флексибилнијим за координацију.
У пракси, мутекс је бржи за једноставно међусобно блокирање захваљујући оптимизацијама за типичан сценарио. Семафор захтева додатне трошкове за одржавање бројача. Међутим, за ограничавање паралелизма или имплементацију обрасца „произвођач-потрошач“, семафор је незаменљив.
| Карактеристика | Semaphore | Мутекс |
|---|---|---|
| Власништво | нема власника | има власника |
| Ослобађање | било која нит | само нит-власник |
| Бројач | од 0 до N | бинаран |
| Употреба | ограничавање паралелизма и сигнализација | заштита критичне секције |
| Рекурзија | не | да (reentrant) |
У развоју мобилних апликација Semaphore се користи за управљање приступом ограниченим ресурсима: мрежним везама, датотекама, базама података и хардверским компонентама. Савремене платформе пружају згодне уграђене имплементације.
Један од типичних случајева — базен HTTP веза. Апликација може истовремено слати највише 4 захтева ка серверу, јер API провајдера ограничава паралелизам. Семафор са почетном вредношћу 4 гарантује да при било ком оптерећењу број истовремених захтева неће прећи границу, а остале нити ће чекати у реду.
Без семафора, при наглом повећању активности корисника, серверска инфраструктура може доживети изненадно преоптерећење, што доводи до тајм-аута и грешака 429 Too Many Requests. Семафор ради као осигурач, пропуштајући строго одређен број истовремених позива без обзира на број активних нити.
Android пружа класу Semaphore из пакета java.util.concurrent. Размотримо пример ограничавања истовремених мрежних захтева на две нити ради спречавања преоптерећења сервера.
class ApiClient {
private val throttle = Semaphore(2)
suspend fun fetch(url: String): Result {
throttle.acquire()
return try {
httpGet(url)
} finally {
throttle.release()
}
}
}
У iOS-у, DispatchSemaphore из GCD решава исти задатак. Програмери га користе за синхронизацију приступа ресурсима у асинхроном коду без блокирања главне нити.
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 при заузимању више семафора у различитом редоследу од различитих нити.
Семафори се примењују не само за заштиту података, већ и за координацију нити у сложеним вишенитним сценаријима. Познавање уобичајених образаца убрзава развој и смањује вероватноћу грешака синхронизације.
Постоји неколико проверених образаца примене семафора у реалним пројектима. Њихово познавање помаже у избегавању типичних грешака и изградњи поузданих вишенитних система.
Семафор са почетном вредношћу N и периодичним release путем тајмера имплементира ограничавање брзине захтева ка API-ју. На пример, сервис дозвољава 10 захтева у секунди: семафор стартује са 10, сваки захтев смањује бројач, а засебан TimerTask једном у секунди враћа бројач на почетну вредност. Ово штити и апликацију и сервер од преоптерећења.
У класичном задатку произвођач-потрошач два семафора управљају бафером: empty (дозволе за писање) и full (дозволе за читање). Произвођач позива acquire на empty и release на full, Потрошач — обрнуто. Ова шема гарантује да Потрошач никада неће прочитати празан бафер, а Произвођач га неће препунити.
Иста шема лежи у основи ограниченог бафера у оперативним системима — кружног бафера фиксне величине. У мобилним апликацијама образац се користи за обраду редова слика, видео датотека и аналитичких догађаја.
Семафори се успешно користе за throttling мрежних позива у позадинским сервисима. На пример, апликација за аналитику шаље пакете догађаја на сервер. Без ограничавања конкурентних нити при вршним оптерећењима (покретање апликације, синхронизација након офлајна) број истовремених захтева може премашити границе сервера. Семафор са почетном вредношћу 3 гарантује глатко слање и спречава блокирање на страни сервера.
Често постављана питања
Семафор није обичан бројач, већ примитив синхронизације са атомским операцијама и редом чекања. Обичан бројач не блокира нит и не гарантује атомскост инкремента при конкурентном приступу више нити.
Да, deadlock је могућ при заузимању више семафора у различитом редоследу од различитих нити. На пример, нит A заузима S1, затим S2, а нит B — S2, затим S1. Утврдите јединствени редослед заузимања за све семафоре у пројекту.
Нит се блокира и прелази у стање чекања. Не троши процесорско време док друга нит не позове release. У Java-и је ово стање BLOCKED, у Swift-у нит се зауставља од стране GCD.
Основна разлика — власништво. Mutex може ослободити само нит-власник. Binary Semaphore може ослободити било која нит, што је згодно за сигнализацију између нити, али мање безбедно за заштиту интегритета података.
Почетна вредност зависи од сценарија. За заштиту једног ресурса — 1. За базен од N веза — N. За сигнализацију између нити користите 0, тако да нит-потрошач чека сигнал од произвођача.
Закључци
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође