Semaphore — bu sinxronlashtirish primitivi bolib, hisoblagich va kutayotgan oqimlar navbati orqali umumiy resursga kirishni boshqaradi. Wikipedia, 2024 malumotiga kora, semafor Edsger Dijkstra tomonidan 1965 yilda kop oqimli ozaro aloqa muammolarini hal qilish uchun taklif qilingan. Asbob kritik bolim bilan bir vaqtda ishlaydigan oqimlar sonini cheklash imkonini beradi.
Asosiy fikrlar
Semaphore — umumiy resursga kirishni boshqarish uchun hisoblagichdan foydalanadigan sinxronlashtirish primitivi. Konsepsiya Edsger Dijkstra tomonidan 1965 yilda taklif qilingan va operatsion tizimlardagi barcha zamonaviy sinxronlashtirish mexanizmlarining asosiga aylangan.
Semafor ikkita atomik operatsiyaga ega butun sonli ozgaruvchidir: wait (acquire) va signal (release). Wait operatsiyasi hisoblagichni kamaytiradi, signal esa uni oshiradi. Hisoblagich nolga etganda, wait ni chaqirgan oqim boshqa oqim tomonidan signal bajarilgunga qadar bloklanadi.
Semaforning asosiy maqsadi kritik bolimlarni bir nechta oqimlarning bir vaqtda kirishidan himoya qilishdir. Mutexdan farqli ravishda, semafor egalik qiluvchi oqimga bog’lanishni talab qilmaydi, bu uni muvofiqlashtirish vazifalarining keng doirasi uchun mos qiladi.
Semafor konsepsiyasi Technische Hogeschool Eindhoven da ishlab chiqilgan THE operatsion tizimi kontekstida paydo bolgan. Dijkstra semaforni matematik abstraksiya sifatida formalashtirib, uning istalgan sinxronlashtirish primitivlarini amalga oshirish uchun yetarliligini isbotladi.
Semafor mexanizmi ikkita atomik operatsiya va ichki kutish navbatiga asoslangan. Acquire chaqiruvida oqim hisoblagich qiymatini tekshiradi va yoki bajarishni davom ettiradi, yoki resurs ozod qilinguncha bloklanadi.
Semafor yaratilganda boshlang’ich ruxsat hisoblagichi qiymati belgilanadi. Har bir acquire chaqiruvi hisoblagichni 1 ga kamaytiradi. Agar bundan keyin hisoblagich manfiy bolsa, oqim bloklanadi. Release operatsiyasi hisoblagichni oshiradi va kutayotgan oqimlardan birini uygotadi.
import java.util.concurrent.Semaphore
val semaphore = Semaphore(3)
fun accessResource() {
semaphore.acquire()
try {
println("${Thread.currentThread().name} ishlayapti")
} finally {
semaphore.release()
}
}
Oqim nol hisoblagich bilan acquire ni chaqirganda, OT uni semaforning FIFO navbatiga joylashtiradi. Oqim BLOCKED holatiga otadi, protsessor vaqtini sarflamaydi. Release chaqiruvidan song navbatdagi birinchi oqim RUNNABLE holatiga otadi va resursga kirish oladi.
Sinxronlashtirish nazariyasida semaforlarning ikkita asosiy turi ajratiladi: ikkilik (binary) va hisoblovchi (counting). Turni tanlash resurslarga kirishni boshqarishning aniq vazifasiga bogliq.
Ikkilik semafor faqat 0 va 1 qiymatlarini oladi. Xattiharakatiga kora mutexni eslatadi, lekin egalik talabisiz — istalgan oqim release ni bajarishi mumkin. Bunday semaforlar oqimlar orasida tayyorlik bayroqlari va hodisalarini amalga oshirish uchun qulaydir.
val ready = Semaphore(0)
fun producer() {
Thread.sleep(1000)
ready.release()
}
fun consumer() {
ready.acquire()
println("Malumotlar tayyor")
}
Hisoblovchi semafor istalgan manfiy bolmagan qiymatni olishi mumkin. Bir nechta nusxasi mavjud bolgan bir turdagi resurslar hovuzini boshqarish uchun ishlatiladi. Masalan, 5 tarmoq ulanishlari hovuzi: har bir acquire bitta ulanishni egallaydi, release uni hovuzga qaytaradi.
Hisoblovchi semaforlar tashqi xizmatlarga kirish tezligini cheklash va oqim hovuzlarini amalga oshirishda ajralmasdir. Ular oqimlarni qol bilan boshqarmasdan parallellik darajasini aniq nazorat qilish imkonini beradi.
| Parametr | Ikkilik semafor | Hisoblovchi semafor |
|---|---|---|
| Diapazon | 0 yoki 1 | 0 dan N gacha |
| Bir vaqtda oqimlar | 1 | N gacha |
| Qollanish | signallash, bayroqlar | resurs hovuzlari, rate limiting |
Dasturchilar kop pin semafor va mutexni aralashtirib yuborishadi, garchi ular orasida fundamental farqlar mavjud bolsa ham. Bu farqlarni tushunish loyihada togri sinxronlashtirish mexanizmini tanlash uchun muhimdir.
Asosiy farq — egalik tushunchasi. Mutex har doim uni qaysi oqim egallaganini biladi va faqat shu oqim uni ozod qilishi mumkin. Semaforning egasi yok: istalgan oqim acquire ni chaqirmasdan ham release ni bajarishi mumkin. Bu mutexni malumotlarni himoya qilish uchun xavfsizroq, semaforni esa muvofiqlashtirish uchun moslashuvchanroq qiladi.
Amaliyotda mutex oddiy ozaro bloklash uchun odatdagi stsenariy optimallashuvlari tufayli tezroq. Semafor hisoblagichni saqlash uchun qoshimcha xarajatlarni talab qiladi. Shunga qaramay, parallellikni cheklash yoki “ishlab chiqaruvchi-iste’molchi” namunasini amalga oshirish uchun semafor ajralmasdir.
| Xususiyat | Semaphore | Mutex |
|---|---|---|
| Egalik | egasi yok | egasi bor |
| Ozod qilish | istalgan oqim | faqat egalik qiluvchi oqim |
| Hisoblagich | 0 dan N gacha | ikkilik |
| Foydalanish | parallellikni cheklash va signallash | kritik bolimni himoya qilish |
| Rekursiya | yok | ha (reentrant) |
Mobil ilovalarni ishlab chiqishda Semaphore cheklangan resurslarga kirishni boshqarish uchun ishlatiladi: tarmoq ulanishlari, fayllar, malumotlar bazalari va apparat komponentlari. Zamonaviy platformalar qulay ichki amalga oshirishlarni taklif qiladi.
Odatdagi holatlardan biri — HTTP ulanishlar hovuzi. Ilova serverga bir vaqtda 4 tadan kop sorov yubora olmaydi, chunki provayder API si parallellikni cheklaydi. Boshlangich qiymati 4 bolgan semafor istalgan yuk ostida bir vaqtda sorovlar soni limitdan oshmasligiga kafolat beradi, qolgan oqimlar navbatda kutadi.
Semafor bolmagan holda, foydalanuvchi faolligi keskin oshganda, server infrastrukturasi tosatdan haddan tashqari yuklanishi mumkin, bu esa timeoutlar va 429 Too Many Requests xatolariga olib keladi. Semafor faol oqimlar sonidan qatiy nazar qatii belgilangan sondagi bir vaqtli chaqiruvlarni otkazuvchi saqlagich kabi ishlaydi.
Android java.util.concurrent paketidan Semaphore sinfini taklif qiladi. Serverning haddan tashqari yuklanishining oldini olish uchun bir vaqtda tarmoq sorovlarini ikkita oqim bilan cheklash misolini korib chiqamiz.
class ApiClient {
private val throttle = Semaphore(2)
suspend fun fetch(url: String): Result {
throttle.acquire()
return try {
httpGet(url)
} finally {
throttle.release()
}
}
}
iOS da GCD dan DispatchSemaphore xuddi shu vazifani hal qiladi. Dasturchilar undan asosiy oqimni bloklamasdan asinxron koddagi resurslarga kirishni sinxronlashtirish uchun foydalanadilar.
let semaphore = DispatchSemaphore(value: 3)
func processBatch(_ items: [UIImage]) {
for img in items {
semaphore.wait()
DispatchQueue.global().async {
applyFilter(to: img)
semaphore.signal()
}
}
}
Eng kop tarqalgan xato — istisno paytida unutulgan release. Agar oqim release chaqirishdan oldin xato bilan tugasa, semafor qolgan oqimlar uchun abadiy bloklangan bolib qoladi. Kafolatlangan ozod qilish uchun try/finally yoki defer dan foydalaning. Ikkinchi muammo — turli oqimlar tomonidan semaforlarni turli tartibda egallashdagi deadlock.
Semaforlar faqat malumotlarni himoya qilish uchun emas, balki murakkab kop oqimli stsenariylarda oqimlarni muvofiqlashtirish uchun ham qollaniladi. Umumiy namunalarni bilish rivojlanishni tezlashtiradi va sinxronlashtirish xatolari ehtimolini kamaytiradi.
Haqiqiy loyihalarda semaforlarni qollashning bir nechta tasdiqlangan namunalari mavjud. Ularni bilish odatdagi xatolardan qochishga va ishonchli kop oqimli tizimlarni qurishga yordam beradi.
Boshlangich qiymati N bolgan semafor va timer orqali davriy release API ga sorovlarning tezligini cheklashni amalga oshiradi. Masalan, xizmat soniyada 10 ta sorovga ruxsat beradi: semafor 10 bilan boshlanadi, har bir sorov hisoblagichni kamaytiradi, alohida TimerTask soniyada bir marta hisoblagichni boshlangich qiymatiga qaytaradi. Bu ilova va serverni haddan tashqari yuklanishdan himoya qiladi.
Klassik ishlab chiqaruvchi-iste’molchi vazifasida ikkita semafor buferni boshqaradi: empty (yozish ruxsati) va full (oqish ruxsati). Ishlab chiqaruvchi empty da acquire va full da release ni chaqiradi, Iste’molchi — aksincha. Bu sxema Iste’molchi hech qachon boshl buferni oqimasligiga va Ishlab chiqaruvchi uni toldirib yubormasligiga kafolat beradi.
Xuddi shu sxema operatsion tizimlarda cheklangan buferning — qatiy olchamli halqa buferining asosida yotadi. Mobil ilovalarda bu namuna tasvirlar, video fayllar va analitik hodisalar navbatlarini qayta ishlash uchun ishlatiladi.
Semaforlar fon xizmatlarida tarmoq chaqiruvlarini trottling qilish uchun muvaffaqiyatli qollaniladi. Masalan, analitika ilovasi serverga hodisalar paketlarini yuboradi. Pik yuklarda (ilovani ishga tushirish, oflayndan keyin sinxronlash) bir vaqtda sorovlar soni server chegaralaridan oshib ketishi mumkin. Boshlangich qiymati 3 bolgan semafor silliq yuborishni kafolatlaydi va server tomonida bloklanishning oldini oladi.
Tez-tez soraluvchi savollar
Semafor oddiy hisoblagich emas, balki atomik operatsiyalar va kutish navbatiga ega sinxronlashtirish primitividir. Oddiy hisoblagich oqimni bloklamaydi va bir nechta oqimlarning raqobatli kirishida inkrementning atomikligini kafolatlamaydi.
Ha, deadlock semaforlarni turli oqimlar tomonidan turli tartibda egallashida mumkin. Masalan, oqim A S1 ni, keyin S2 ni, oqim B esa S2 ni, keyin S1 ni egallaydi. Loyihada barcha semaforlar uchun yagona egallash tartibini belgilang.
Oqim bloklanadi va kutish holatiga otadi. Boshqa oqim release chaqirmaguncha protsessor vaqtini sarflamaydi. Java da bu BLOCKED holati, Swift da oqim GCD tomonidan totatiladi.
Asosiy farq — egalik. Mutex faqat egalik qiluvchi oqim tomonidan ozod qilinishi mumkin. Binary Semaphore istalgan oqim tomonidan ozod qilinishi mumkin, bu oqimlar orasida signallash uchun qulay, lekin malumotlar yaxlitligini himoya qilish uchun kamroq xavfsiz.
Boshlangich qiymat stsenariydan bogliq. Bitta resursni himoya qilish uchun — 1. N ta ulanish hovuzi uchun — N. Oqimlar orasida signallash uchun 0 dan foydalaning, shunda iste’molchi oqim ishlab chiqaruvchidan signal kutadi.
Xulosa
Biz kalit topshirig'i bilan mobil ilovani ishlab chiqamiz
IT Sectr 2017-yildan beri startaplar va korxonalar uchun iOS va Android ilovalarini yaratadi. Biz sizga maslahat beramiz va eng yaxshi yechimni taklif qilamiz.