Semaphore — sinxronizasiya primitividir, sayğac və gözləyən thread-lar növbəsi vasitəsilə ümumi resursa girişi idarə edir. Wikipedia, 2024-ə görə, semafor Edsger Dijkstra tərəfindən 1965-ci ildə çoxiplikli qarşılıqlı əlaqə problemlərini həll etmək üçün təklif edilmişdir. Alət kritik bölmə ilə eyni vaxtda işləyən thread-ların sayını məhdudlaşdırmağa imkan verir.
Əsas məqamlar
Semaphore — paylaşılan resursa girişi idarə etmək üçün sayğacdan istifadə edən sinxronizasiya primitividir. Konsepsiya Edsger Dijkstra tərəfindən 1965-ci ildə təklif edilmiş və əməliyyat sistemlərində bütün müasir sinxronizasiya mexanizmlərinin əsasına çevrilmişdir.
Semafor iki atomik əməliyyatı olan tamədədli dəyişəndir: wait (acquire) və signal (release). Wait əməliyyatı sayğacı azaldır, signal isə artırır. Sayğac sıfıra êtdıqda, wait-i çağıran thread başqa thread tərəfindən signal yerinə yetirilənə qədər bloklanır.
Semaforun əsas məqsədi kritik bölmələri bir neçə thread-ın eyni vaxtda girişindən qorumaqdır. Mutexdən fərqli olaraq, semafor sahib thread-a bağlanmağı tələb etmir, bu da onu daha geniş əlaqələndirmə tapşırıqları üçün əlverişli edir.
Semafor konsepsiyası Technische Hogeschool Eindhoven-də hazırlanmış THE əməliyyat sistemi kontekstində yaranmışdır. Dijkstra semaforu riyazi abstraksiya kimi formalizasiya etmiş, onun istənilən sinxronizasiya primitivlərini həyata keçirmək üçün kifayət olduğunu sübut etmişdir.
Semafor mexanizmi iki atomik əməliyyat və daxili gözləmə növbəsi üzərində qurulub. Acquire çağırışı zamanı thread sayğacın dəyərini yoxlayır və ya icraya davam edir, ya da resurs boşalana qədər bloklanır.
Semafor yaradılarkən ilkin icazə sayğacı dəyəri təyin edilir. Hər acquire çağırışı sayğacı 1 azaldır. Bundan sonra sayğac mənfi olarsa, thread bloklanır. Release əməliyyatı sayğacı artırır və gözləyən thread-lardan birini oyadır.
import java.util.concurrent.Semaphore
val semaphore = Semaphore(3)
fun accessResource() {
semaphore.acquire()
try {
println("${Thread.currentThread().name} işləyir")
} finally {
semaphore.release()
}
}
Thread sıfır sayğacla acquire çağırdıqda, ƏS onu semaforun FIFO növbəsinə yerləşdirir. Thread BLOCKED vəziyyətinə keçir, prosessor vaxtı sərf etmir. Release çağırışından sonra növbədəki ilk thread RUNNABLE vəziyyətinə keçir və resursa giriş əldə edir.
Sinxronizasiya nəzəriyyəsində semaforların iki əsas növü fərqləndirilir: binary (ikili) və counting (sayğac). Növün seçimi resurslara girişin idarə edilməsinin konkret tapşırığından asılıdır.
Binary semafor yalnız 0 və 1 qiymətlərini alır. Davranışına görə mutexi xatırladır, lakin sahiblik tələbi olmadan — istənilən thread release edə bilər. Belə semaforlar thread-lar arasında hazırlıq bayraqları və hadisələri həyata keçirmək üçün əlverişlidir.
val ready = Semaphore(0)
fun producer() {
Thread.sleep(1000)
ready.release()
}
fun consumer() {
ready.acquire()
println("Məlumatlar hazırdır")
}
Sayğac semaforu istənilən qeyri-mənfi qiymət ala bilər. Bir neçə nüsxəsi mövcud olan eynitipli resurslar hovuzunu idarə etmək üçün istifadə olunur. Məsələn, 5 şəbəkə bağlantısı hovuzu: hər acquire bir bağlantını tutur, release onu hovuza qaytarır.
Sayğac semaforları xarici xidmətlərə giriş sürətini məhdudlaşdırmaq və thread hovuzlarını həyata keçirmək üçün əvəzolunmazdır. Onlar thread-ları əl ilə idarə etmədən paralellik dərəcəsini dəqiq nəzarət etməyə imkan verir.
| Parametr | Binary semafor | Sayğac semaforu |
|---|---|---|
| Diapazon | 0 və ya 1 | 0-dan N-ə qədər |
| Eyni vaxtda thread-lar | 1 | N-ə qədər |
| Tətbiq | siqnalizasiya, bayraqlar | resurs hovuzları, rate limiting |
Tərtibatçılar tez-tez semafor və mutexi qarışdırırlar, baxmayaraq ki, aralarında fundamental fərqlər var. Bu fərqləri anlamaq layihədə düzgün sinxronizasiya mexanizmini seçmək üçün kritik əhəmiyyət daşıyır.
Əsas fərq — sahiblik anlayışı. Mutex həmişə onu hansı threadın tutduğunu bilir və yalnız həmin thread onu sərbəst buraxa bilər. Semaforun sahibi yoxdur: istənilən thread acquire çağırmadan da release edə bilər. Bu, mutexi məlumatların qorunması üçün daha təhlükəsiz, semaforu isə koordinasiya üçün daha çevik edir.
Təcrübədə mutex tipik ssenari üçün optimallaşdırmalar sayəsində sadə qarşılıqlı bloklama üçün daha sürətlidir. Semafor sayğacın saxlanması üçün əlavə yük tələb edir. Bununla belə, paralelliyi məhdudlaşdırmaq və ya “istehsalçı-istehlakçı” nümunəsini həyata keçirmək üçün semafor əvəzolunmazdır.
| Xüsusiyyət | Semaphore | Mutex |
|---|---|---|
| Sahiblik | sahib yoxdur | sahib var |
| Azad etmə | istənilən thread | yalnız sahib thread |
| Sayğac | 0-dan N-ə qədər | binary |
| Use case | paralelliyin məhdudlaşdırılması və siqnalizasiya | kritik bölmənin qorunması |
| Rekursiya | yox | bəli (reentrant) |
Mobil tətbiqlərin hazırlanmasında Semaphore məhdud resurslara girişi idarə etmək üçün istifadə olunur: şəbəkə bağlantıları, fayllar, verilənlər bazaları və aparat komponentləri. Müasir platformalar rahat daxili tətbiqlər təqdim edir.
Tipik hallardan biri HTTP bağlantıları hovuzudur. Tətbiq serverə eyni vaxtda 4-dən çox sorğu göndərə bilməz, çünki provayderin API-i paralelliyi məhdudlaşdırır. İlkin dəyəri 4 olan semafor istənilən yük altında eyni vaxtda sorğuların sayının limiti keçməyəcəyinə zəmanət verir, qalan thread-lar növbədə gözləyir.
Semafor olmadan istifadəçi aktivliyinin kəskin artması zamanı server infrastrukturu qəfil həddindən artıq yüklənə bilər, bu da timeoutlara və 429 Too Many Requests səhvlərinə səbəb olur. Semafor aktiv thread-ların sayından asılı olmayaraq ciddi şəkildə müəyyən edilmiş sayda eyni vaxtlı çağırışları buraxan qoruyucu kimi işləyir.
Android java.util.concurrent paketindən Semaphore sinfini təqdim edir. Serverin həddindən artıq yüklənməsinin qarşısını almaq üçün eyni vaxtda şəbəkə sorğularını iki threadla məhdudlaşdırmaq nümunəsinə baxaq.
class ApiClient {
private val throttle = Semaphore(2)
suspend fun fetch(url: String): Result {
throttle.acquire()
return try {
httpGet(url)
} finally {
throttle.release()
}
}
}
iOS-də GCD-dən DispatchSemaphore eyni tapşırığı həll edir. Tərtibatçılar onu asinxron koddakı resurslara girişi sinxronlaşdırmaq üçün əsas thread-ı bloklamadan istifadə edirlər.
let semaphore = DispatchSemaphore(value: 3)
func processBatch(_ items: [UIImage]) {
for img in items {
semaphore.wait()
DispatchQueue.global().async {
applyFilter(to: img)
semaphore.signal()
}
}
}
Ən çox yayılmış səhv — istisna zamanı unutulmuş release-dir. Thread release çağırmadan xəta ilə bitərsə, semafor qalan thread-lar üçün əbədi olaraq bloklanmış qalır. Zəmanətli sərbəst buraxma üçün try/finally və ya defer istifadə edin. İkinci problem — müxtəlif thread-lar tərəfindən fərqli sırada bir neçə semaforun tutulması zamanı deadlock-dur.
Semaforlar təkcə məlumatların qorunması üçün deyil, həm də mürəkkəb çoxiplikli ssenarilərdə thread-ların koordinasiyası üçün tətbiq olunur. Ümumi nümunələri bilmək inkişafı sürətləndirir və sinxronizasiya səhvləri ehtimalını azaldır.
Real layihələrdə semaforların tətbiqinin bir neçə sübut olunmuş nümunəsi mövcuddur. Onları bilmək tipik səhvlərdən qaçmağa və etibarlı çoxiplikli sistemlər qurmağa kömək edir.
İlkin dəyəri N olan semafor və timer vasitəsilə dövri release API-ə sorğuların sürət məhdudlaşdırılmasını həyata keçirir. Məsələn, xidmət saniyədə 10 sorğuya icazə verir: semafor 10 ilə başlayır, hər sorğu sayğacı azaldır, ayrıca TimerTask saniyədə bir dəfə sayğacı ilkin dəyərinə qaytarır. Bu, həm tətbiqi, həm də serveri həddindən artıq yükdən qoruyur.
Klassik istehsalçı-istehlakçı tapşırığında iki semafor buferi idarə edir: empty (yazma icazəsi) və full (oxuma icazəsi). İstehsalçı empty-də acquire və full-da release çağırır, İstehlakçı — əksinə. Bu sxem İstehlakçının heç vaxt boş buferi oxumayacağına, İstehsalçının isə onu daşdırmayacağına zəmanət verir.
Eyni sxem əməliyyat sistemlərində məhdud buferin — sabit ölçülü halqa buferinin əsasını təşkil edir. Mobil tətbiqlərdə bu nümunə şəkillər, video fayllar və analitik hadisələr növbələrini emal etmək üçün istifadə olunur.
Semaforlar fon xidmətlərində şəbəkə çağırışlarının throttling-i üçün uğurla istifadə olunur. Məsələn, analitika tətbiqi serverə hadisə paketləri göndərir. Pik yüklərdə (tətbiqin işə salınması, oflayn sinxronizasiya) eyni vaxtda sorğuların sayı server limitlərini keçə bilər. İlkin dəyəri 3 olan semafor hamar göndərməyə zəmanət verir və server tərəfində blokadanın qarşısını alır.
Tez-tez verilən suallar
Semafor sadəcə sayğac deyil, atomik əməliyyatları və gözləmə növbəsi olan sinxronizasiya primitividir. Adi sayğac thread-ı bloklamır və bir neçə thread-ın rəqabətli girişi zamanı inkrementin atomikliyinə zəmanət vermir.
Bəli, deadlock müxtəlif thread-lar tərəfindən semaforların fərqli sırada tutulması zamanı mümkündür. Məsələn, thread A S1-i, sonra S2-ni, thread B isə S2-ni, sonra S1-i tutur. Layihədə bütün semaforlar üçün vahid tutma sırası təyin edin.
Thread bloklanır və gözləmə vəziyyətinə keçir. Başqa thread release çağırana qədər prosessor vaxtı sərf etmir. Java-da bu BLOCKED vəziyyətidir, Swift-də thread GCD tərəfindən dayandırılır.
Əsas fərq — sahiblik. Mutex yalnız sahib thread tərəfindən sərbəst buraxıla bilər. Binary Semaphore istənilən thread tərəfindən sərbəst buraxıla bilər, bu thread-lar arasında siqnalizasiya üçün əlverişlidir, lakin məlumatların bütövlüyünü qorumaq üçün daha az təhlükəsizdir.
İlkin dəyər ssenaridən asılıdır. Bir resursu qorumaq üçün — 1. N bağlantıdan ibarət hovuz üçün — N. Thread-lar arasında siqnalizasiya üçün 0 istifadə edin ki, istehlakçı thread istehsalçıdan siqnal gözləsin.
Xülasə
Açar təslim mobil tətbiq hazırlayacağıq
IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.
Həm də oxuyun