select operatori
select bir nechta channel amali orasidan ayni paytda bajarishga tayyor bo‘lgan bittasini tanlaydi.
Oddiy channel amali faqat bitta yuborish yoki qabul qilish operatsiyasini kutadi. select esa bir vaqtning o‘zida bir nechta channelni kuzatishga imkon beradi.
Masalan, backend xizmat quyidagi ikki hodisani bir vaqtda kutishi mumkin:
- tashqi API’dan natija kelishi;
- foydalanuvchi so‘rovni bekor qilishi.
Qaysi hodisa birinchi tayyor bo‘lsa, select o‘sha holatga tegishli kodni bajaradi.
Shu sababli select odatda:
- bir nechta goroutinedan kelayotgan natijalarni kutishda;
- timeout, ya’ni vaqt chegarasini qo‘yishda;
contextorqali bekor qilish signalini kuzatishda;- bir nechta channel orasidan tayyor bo‘lganini tanlashda
ishlatiladi.
Asosiy sintaksis
select tashqi ko‘rinishidan switchga o‘xshaydi. Lekin uning caselari oddiy shartlarni emas, channel operatsiyalarini kutadi:
select {
case msg := <-messages:
fmt.Println("Qabul qilindi:", msg)
case results <- value:
fmt.Println("Natija yuborildi")
default:
fmt.Println("Hozircha tayyor kanal yo‘q")
}
Bu yerda uchta imkoniyat bor.
Birinchi case:
messages channelidan qiymat qabul qilishga urinadi.
Ikkinchi case:
value qiymatini results channeliga yuborishga urinadi.
default esa hech bir channel operatsiyasi hozir bajarishga tayyor bo‘lmasa ishlaydi.
select bajarilganda quyidagi qoidalar amal qiladi:
- faqat bitta
casetayyor bo‘lsa, aynan o‘shacasebajariladi; - bir nechta
casebir vaqtda tayyor bo‘lsa, Go ular orasidan bittasini pseudo-random tarzda tanlaydi; - hech bir
casetayyor bo‘lmasa vadefaultmavjud bo‘lmasa, joriy goroutine bloklanadi; - hech bir
casetayyor bo‘lmasa vadefaultmavjud bo‘lsa,defaultdarhol bajariladi.
Bu yerda muhim bir qoida bor: caselarning kod ichidagi tartibi ularga ustuvorlik bermaydi.
Masalan:
agar first ham, second ham qabul qilish uchun tayyor bo‘lsa, yuqorida yozilgani uchun first avtomatik tanlanmaydi.
select yuqoridan pastga yurib, birinchi tayyor caseni tanlamaydi. Bir nechta operatsiya tayyor bo‘lsa, ulardan bittasi tanlanadi.
Birinchi ishlaydigan misol
Quyidagi misolda ikkita goroutine turli vaqtda natija yuboradi:
package main
import (
"fmt"
"time"
)
func main() {
fast := make(chan string)
slow := make(chan string)
go func() {
time.Sleep(100 * time.Millisecond)
fast <- "tez natija"
}()
go func() {
time.Sleep(200 * time.Millisecond)
slow <- "sekin natija"
}()
select {
case value := <-fast:
fmt.Println(value)
case value := <-slow:
fmt.Println(value)
}
}
Odatdagi natija:
Jarayonni bosqichma-bosqich ko‘ramiz.
Avval ikkita buffersiz channel yaratiladi:
Keyin birinchi goroutine ishga tushadi:
U taxminan 100 millisekund kutadi va fast channeliga qiymat yubormoqchi bo‘ladi.
Ikkinchi goroutine esa:
taxminan 200 millisekunddan keyin slow channeliga qiymat yuboradi.
main goroutine esa quyidagi selectga keladi:
Bu paytda hech bir qiymat hali kelmagan bo‘lsa, select bloklanadi. U ikkala channeldan birortasi qabul qilish uchun tayyor bo‘lishini kutadi.
Taxminan 100 millisekunddan keyin fast channeliga yuborish operatsiyasi paydo bo‘ladi. Shu sabab:
bajarishga tayyor bo‘ladi va select shu caseni tanlaydi.
Ikkala channel ham buffersiz. Buffersiz channelda yuboruvchi va qabul qiluvchi bir-biriga mos kelishi kerak. Yuboruvchi qiymatni shunchaki channel ichiga tashlab, davom eta olmaydi. U qabul qiluvchi tayyor bo‘lishini kutadi.
Bu misolda uyqu vaqtlari ataylab turlicha berilgan. Shu sabab odatda fast birinchi tanlanadi.
Lekin quyidagini eslab qolish kerak: agar ikkala channel operatsiyasi bir vaqtda tayyor bo‘lsa, qaysi case tanlanishini koddagi tartibga qarab oldindan aytib bo‘lmaydi.
Yana bir muhim nuqta bor. Bu yerda select faqat bir marta bajarilgan.
fastdan qiymat olingach, select tugaydi va main funksiyasi yakunlanadi. Go main tugaganda boshqa goroutinelarning ham albatta yakunlanishini kutmaydi.
Shu sababli dastur slow channelidan ikkinchi natijani qabul qilmaydi.
Bir nechta natijani qabul qilish
Agar ikkala natija ham kerak bo‘lsa, selectni bir marta bajarish yetarli emas.
Uni kerakli miqdorda takrorlash mumkin:
package main
import (
"fmt"
"time"
)
func main() {
first := make(chan string)
second := make(chan string)
go func() {
time.Sleep(100 * time.Millisecond)
first <- "birinchi kanal"
}()
go func() {
time.Sleep(200 * time.Millisecond)
second <- "ikkinchi kanal"
}()
for i := 0; i < 2; i++ {
select {
case value := <-first:
fmt.Println(value)
case value := <-second:
fmt.Println(value)
}
}
}
Natija:
Bu yerda asosiy farq:
siklida.
select ikki marta bajariladi.
Birinchi aylanishda first taxminan 100 millisekunddan keyin tayyor bo‘ladi:
qiymati olinadi.
Shundan keyin sikl ikkinchi marta ishlaydi. select yana kanallardan birortasi tayyor bo‘lishini kutadi.
Bu safar qolgan second channelidan:
qiymati olinadi.
Demak, selectning o‘zi barcha tayyor natijalarni birdaniga yig‘ib bermaydi. Har bir bajarilishida ko‘pi bilan bitta case tanlanadi.
Agar nechta natija kelishi oldindan ma’lum bo‘lsa, bu misoldagidek sanash mumkin.
Lekin natijalar soni oldindan noma’lum bo‘lsa, odatda channelning yopilganini kuzatish kerak. Bunday vaziyatda value, ok := <-ch shakli yordam beradi. Buni keyinroq yopilgan channel bo‘limida ko‘ramiz.
Bloklanish nima?
Channel bilan ishlaganda bloklanish tushunchasini aniq tushunish muhim.
Goroutine bir operatsiyani bajarish uchun kerakli sharoit paydo bo‘lishini kutib qolsa, u bloklangan hisoblanadi.
Buffersiz channelda yuborish va qabul qilish bir-biriga mos kelishi kerak.
Yuborish:
bajarilishi uchun qabul qiluvchi mavjud bo‘lishi kerak.
Qabul qilish:
bajarilishi uchun esa yuboruvchi mavjud bo‘lishi kerak.
Aks holda operatsiyani bajarayotgan goroutine kutadi.
Quyidagi kodda main goroutine buffersiz channelga qiymat yubormoqchi:
package main
func main() {
ch := make(chan int)
ch <- 10 // Qabul qiluvchi yo‘q, shu sababli runtime deadlockni aniqlaydi.
}
Bu ataylab noto‘g‘ri yozilgan misol.
Jarayon quyidagicha:
chnomli buffersiz channel yaratiladi.maingoroutine10qiymatini yuborishga keladi.- Lekin hech qanday boshqa goroutine
chdan qiymat qabul qilmayapti. - Shu sabab
mainshu qatorning o‘zida bloklanadi. - Ishlay oladigan boshqa goroutine ham yo‘q.
- Go runtime dastur davom eta olmasligini aniqlaydi.
Natijada dastur odatda quyidagi xato bilan tugaydi:
Muammoni bir nechta usul bilan hal qilish mumkin.
Masalan, qabul qiluvchi goroutine ishga tushirish mumkin:
Yoki vazifa shunga mos bo‘lsa, channelga buffer berish mumkin.
Lekin buffer qo‘shish har bir deadlock uchun universal yechim emas. To‘g‘ri yechim ma’lumot qayerdan kelishi, kim qabul qilishi va goroutinelar qanday sinxronlashtirilishiga bog‘liq.
Bloklanmaydigan kanal amali
Ba’zan channel tayyor bo‘lmasa, kutish kerak emas. Dastur boshqa ishini davom ettirishi kerak bo‘ladi.
Bunday holatda select ichidagi default ishlatilishi mumkin:
package main
import "fmt"
func main() {
ch := make(chan int, 1)
select {
case value := <-ch:
fmt.Println("Qabul qilindi:", value)
default:
fmt.Println("Kanalda qiymat yo‘q")
}
}
Natija:
Bu yerda channel bufferli:
lekin uning ichiga hech qanday qiymat yozilmagan.
Shuning uchun:
hozir bajarishga tayyor emas.
Agar default bo‘lmaganida, select qiymat kelishini kutib bloklanardi.
Lekin bu yerda:
mavjud. Shu sabab select kutmaydi va default darhol bajariladi.
Bunday yondashuv non-blocking channel operatsiyasi deb ataladi. Ya’ni dastur channel tayyor bo‘lmasa ham shu joyda kutib qolmaydi.
Bu usul masalan telemetriya, statistik ma’lumot yoki tashlab yuborilishi mumkin bo‘lgan signal uchun foydali bo‘lishi mumkin.
Lekin muhim ma’lumotlar uchun ehtiyot bo‘lish kerak.
Masalan:
ko‘rinishidagi kod producerga kutmasdan davom etish imkonini beradi. Agar jobs channeli tayyor bo‘lmasa, job tashlab yuboriladi.
Agar bu biznes uchun muhim vazifa bo‘lsa, bunday xatti-harakat ma’lumot yo‘qolishiga olib kelishi mumkin.
default yana backpressurega ham ta’sir qiladi.
Backpressure — consumer producer tezligiga yetisha olmaganda, producerning tabiiy ravishda sekinlashishi yoki kutib qolishi.
Agar default orqali yuborishni doim bloklanmaydigan qilsak, bu tabiiy kutish mexanizmini chetlab o‘tishimiz mumkin.
Warning
for sikli ichida faqat select va default bo‘lsa, hech qanday bloklovchi operatsiya bo‘lmasligi mumkin. Natijada sikl juda tez qayta-qayta aylanadi. Bu holat busy loop deb ataladi va CPU vaqtini bekorga sarflashi mumkin.
Masalan:
```go for { select { default: } }
bu sikl hech narsani kutmaydi. U imkon qadar tez aylanaveradi.
Vaqt chegarasi
Tashqi xizmatdan javobni yoki uzoq davom etadigan hisoblashni cheksiz kutish ko‘pincha yaxshi yechim emas.
Masalan, tashqi API javob bermay qolsa, butun request handler uzoq vaqt kutib turishi mumkin.
selectga timer channelini qo‘shib, operatsiyaga vaqt chegarasi berish mumkin:
package main
import (
"fmt"
"time"
)
func main() {
result := make(chan string, 1)
go func() {
time.Sleep(200 * time.Millisecond)
result <- "tayyor"
}()
timer := time.NewTimer(100 * time.Millisecond)
defer timer.Stop()
select {
case value := <-result:
fmt.Println(value)
case <-timer.C:
fmt.Println("vaqt tugadi")
}
}
Natija:
Bu kodni bosqichma-bosqich ko‘ramiz.
Avval natija uchun channel yaratiladi:
Uning sig‘imi 1. Demak, bir dona qiymat buffer ichiga joylanishi mumkin.
Keyin goroutine ishga tushadi:
Bu goroutine 200 millisekunddan keyin natijani yuboradi.
Ammo timeout 100 millisekundga o‘rnatilgan:
timer.C — vaqt tugaganda qiymat yuboradigan channel.
select esa ikkita hodisani kutmoqda:
Taxminan 100 millisekunddan keyin timer birinchi bo‘lib tayyor bo‘ladi.
Shu sabab:
tanlanadi va:
chiqariladi.
Bu misolda result channelining bufferli ekaniga alohida e’tibor berish kerak.
Agar u buffersiz bo‘lganida:
main timeout sababli selectdan chiqib ketganidan keyin worker goroutine:
qatoriga kelishi mumkin edi.
Lekin endi resultdan qiymat qabul qiladigan goroutine qolmagan bo‘ladi. Buffersiz channel sabab yuboruvchi shu yerda bloklanib qolishi mumkin.
Sig‘imi 1 bo‘lgan buffer workerga qiymatni joylashtirib, ishini tugatish imkonini beradi.
Bu timeoutning juda muhim xususiyatini ko‘rsatadi:
timeout kutishni to‘xtatadi, lekin ishlayotgan goroutineni avtomatik ravishda bekor qilmaydi.
Agar hisoblashning o‘zini ham to‘xtatish kerak bo‘lsa, odatda context yoki alohida bekor qilish signali ishlatiladi.
Bitta oddiy kutish uchun time.After ham ishlatish mumkin:
select {
case value := <-result:
fmt.Println(value)
case <-time.After(100 * time.Millisecond):
fmt.Println("vaqt tugadi")
}
Bu qisqa va o‘qilishi qulay.
Lekin uzoq davom etadigan siklda har aylanishda yangi time.After yaratish yangi timer obyektlarini yaratadi.
Timerning lifecycle’ini aniqroq boshqarish kerak bo‘lsa, time.NewTimer ishlatish va kerakli vaziyatda uni Reset qilish ma’qulroq bo‘lishi mumkin.
Davriy signal kerak bo‘lsa esa time.NewTicker ishlatiladi.
context orqali bekor qilish
Real backend kodida faqat timeoutni kutishning o‘zi yetarli bo‘lmaydi.
Ko‘pincha ishlayotgan operatsiyani ham bekor qilish kerak.
Bunday signal Go’da odatda context.Context orqali uzatiladi.
Context ichidagi:
bekor qilish yoki deadline tugaganda yopiladigan channel qaytaradi.
Channel yopilgach, undan qabul qilish darhol tayyor bo‘ladi. Shu sabab ctx.Done()ni select ichida kutish juda qulay.
Context yaratish, timeout, deadline va cancel() qoidalari context asoslari darsida batafsil tushuntiriladi.
Quyidagi misolda worker davriy ravishda qadam bajaradi, lekin context bekor qilinsa ishni to‘xtatadi:
package main
import (
"context"
"fmt"
"time"
)
func work(ctx context.Context) error {
ticker := time.NewTicker(20 * time.Millisecond)
defer ticker.Stop()
for step := 1; step <= 10; step++ {
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
fmt.Println("qadam:", step)
}
}
return nil
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
if err := work(ctx); err != nil {
fmt.Println("ish to‘xtadi:", err)
}
}
Bu kodda:
har taxminan 20 millisekundda signal beradi.
Worker sikli:
eng ko‘pi bilan 10 ta qadam bajarishga harakat qiladi.
Har aylanishda select ikkita hodisani kutadi:
yoki:
Agar ticker birinchi tayyor bo‘lsa:
bajariladi.
mainda esa context uchun 50 millisekundlik timeout berilgan:
Taxminan 50 millisekunddan keyin deadline tugaydi va ctx.Done() yopiladi.
Shundan keyin:
bajarilishi mumkin.
ctx.Err() bu holatda:
xatosini qaytaradi.
Shu sabab yakuniy satr:
ko‘rinishida bo‘ladi.
Undan oldin nechta:
satri chiqishi scheduler va timerlarning amalda qachon ishga tushishiga bog‘liq bo‘lishi mumkin. Shu sabab aniq qadamlar soniga tayanmaslik kerak.
Bu pattern real dasturlarda juda ko‘p uchraydi.
Masalan:
- HTTP request client tomonidan bekor qilinganda;
- database query deadline’dan oshib ketganda;
- bir nechta goroutine bitta umumiy signal orqali to‘xtatilishi kerak bo‘lganda;
- server shutdown paytida workerlarni boshqarishda.
Bu yerda asosiy qoida shuki, worker contextni shunchaki qabul qilib qo‘ymasligi kerak. U kerakli joylarda ctx.Done()ni kuzatishi yoki contextni context-aware API’larga uzatishi kerak.
Yopilgan va nil kanal
select bilan ishlaganda yopilgan channel va nil channel orasidagi farq juda muhim.
Ular deyarli qarama-qarshi xatti-harakat qiladi:
- yopilgan channeldan qabul qilish bloklanmaydi;
nilchanneldan qabul qilish esa bloklanadi.
Avval yopilgan channelni ko‘ramiz.
Channel yopilgach, uning bufferida qolgan qiymatlar avval o‘qiladi. Buffer tugagach, undan yana qabul qilinsa channel element turining zero value qiymati qaytadi.
Channel haqiqatan yopilganini tekshirish uchun ikki qiymatli qabul qilish ishlatiladi:
Bu yerda:
value— olingan qiymat;ok— qabul qilish muvaffaqiyatli qiymat berganini bildiradi.
Agar:
bo‘lsa, channel yopilgan va uning bufferida boshqa qiymat qolmagan.
Bu tekshiruv ayniqsa int, bool, pointer yoki boshqa zero value’ga ega turlarda muhim.
Masalan, int channel uchun yopilgandan keyingi qiymat:
bo‘ladi.
Lekin 0 haqiqiy biznes ma’lumot ham bo‘lishi mumkin. okni tekshirmasak, yopilgan channeldan kelgan zero value’ni haqiqiy ma’lumot deb qabul qilishimiz mumkin.
Endi selectdagi muammoni ko‘ramiz.
Yopilgan channeldan qabul qilish bloklanmagani uchun uning casei doim tayyor turadi.
Agar bir nechta channelni siklda o‘qiyotgan bo‘lsak, yopilgan channel qayta-qayta selectga tushishi mumkin.
Buni to‘xtatish uchun channel o‘zgaruvchisiga nil berish mumkin.
nil channelga yuborish ham, undan qabul qilish ham oddiy holatda abadiy bloklanadi. select ichida esa bunday channelga tegishli case tanlanmaydi.
Quyidagi misol shu usulni ko‘rsatadi:
package main
import "fmt"
func main() {
left := make(chan int, 1)
right := make(chan int, 1)
left <- 10
right <- 20
close(left)
close(right)
for left != nil || right != nil {
select {
case value, ok := <-left:
if !ok {
left = nil
continue
}
fmt.Println("left:", value)
case value, ok := <-right:
if !ok {
right = nil
continue
}
fmt.Println("right:", value)
}
}
}
Natija satrlarining tartibi o‘zgarishi mumkin:
Avval ikkala bufferli channelga bittadan qiymat yoziladi:
Keyin ikkalasi yopiladi:
Channel yopilishi uning ichidagi mavjud buffered qiymatlarni yo‘q qilib yubormaydi.
Shuning uchun leftdan hali ham 10, rightdan esa 20 olish mumkin.
Sikl shunday davom etadi:
Hech bo‘lmaganda bitta channel faol ekan, sikl ishlaydi.
Channelning qiymati tugab, yopilgan holati aniqlansa:
o‘zgaruvchiga nil beriladi.
Shundan keyin:
amaliy jihatdan o‘chiriladi, chunki left endi nil.
Xuddi shu jarayon right uchun ham bajariladi.
Ikkalasi ham:
bo‘lgach, sikl sharti yolg‘on bo‘ladi va sikl tugaydi.
Agar yopilgan channelni nilga o‘tkazmasak, uning receive casei doim tayyor bo‘lib qoladi. Natijada select uni yana va yana tanlashi va zero value qaytarishi mumkin.
Bu select bilan bir nechta channelni dinamik boshqarishda juda foydali pattern.
Bo‘sh select
Go’da quyidagi kod ham to‘g‘ri sintaksis hisoblanadi:
Bu selectda:
caseyo‘q;defaultyo‘q.
Demak, bajarishga tayyor bo‘lishi mumkin bo‘lgan hech qanday operatsiya ham yo‘q.
Natijada joriy goroutine abadiy bloklanadi.
Masalan:
main goroutine shu yerda tugamasdan turadi.
Ba’zan dasturda fon goroutinelarni ishga tushirib, mainni sun’iy ravishda ochiq qoldirish uchun bunday kodni uchratish mumkin.
Lekin amaliy dasturda bu ko‘pincha eng yaxshi boshqaruv usuli emas.
Sababi select {} fon goroutinelarning holatini bilmaydi.
Masalan:
- worker xato bilan tugagan bo‘lishi mumkin;
- barcha goroutinelar allaqachon ishlashni to‘xtatgan bo‘lishi mumkin;
- server shutdown qilinishi kerak bo‘lishi mumkin.
Lekin main baribir abadiy kutib turadi.
Shu sabab goroutinelarning lifecycle’ini:
sync.WaitGroup;- channel;
context
yordamida boshqarish odatda aniqroq.
Bu vositalar nafaqat kutish, balki ishning qachon va nima sababdan tugayotganini boshqarishga ham yordam beradi.
Keng tarqalgan xatolar
select sintaksisi sodda ko‘rinsa ham, uning xatti-harakatini noto‘g‘ri tushunish oson.
Quyidagi xatolar ayniqsa ko‘p uchraydi.
select barcha tayyor caselarni bajaradi deb o‘ylash
Bu noto‘g‘ri.
select bir marta bajarilganda ko‘pi bilan bitta case tanlaydi.
Masalan, uchta channel bir vaqtda tayyor bo‘lsa ham:
faqat bittasi bajariladi.
Agar barcha natijalarni olish kerak bo‘lsa, select odatda sikl ichida ishlatiladi.
Yuqoridagi case ustun deb hisoblash
Bu ham noto‘g‘ri.
Quyidagi kodda:
first yuqorida yozilgani uchun ustunlikka ega emas.
Agar ikkala operatsiya ham tayyor bo‘lsa, Go tayyor operatsiyalar orasidan bittasini tanlaydi.
Kod tartibi priority sifatida ishlamaydi.
Agar aniq priority kerak bo‘lsa, uni alohida boshqaruv logikasi bilan ifodalash kerak.
Yopilgan channeldagi ok qiymatini tekshirmaslik
Quyidagi qabul qilish:
channel yopilganini o‘zi ko‘rsatmaydi.
Channel yopilib, buffer ham bo‘sh bo‘lsa, valuega element turining zero value’i keladi.
Shu sabab yopilish muhim bo‘lgan joyda:
shaklidan foydalanish kerak.
Aks holda zero value haqiqiy ma’lumot deb qabul qilinishi mumkin.
defaultni har joyda ishlatish
default bloklanishni yo‘q qiladi.
Bu ba’zi vaziyatlarda juda foydali. Lekin uni avtomatik ravishda har bir selectga qo‘shish yaxshi fikr emas.
Masalan:
producerga consumer’ni kutmaslik imkonini beradi.
Bu esa tabiiy backpressure yo‘qolishiga yoki ma’lumotning tashlab yuborilishiga olib kelishi mumkin.
Shuning uchun default qo‘shayotganda quyidagi savolni berish kerak:
Agar channel hozir tayyor bo‘lmasa, bu operatsiyani tashlab yuborish yoki kutmasdan davom etish haqiqatan ham to‘g‘rimi?
Timeoutdan keyin yuboruvchi goroutineni unutish
Timeout faqat select kutishini to‘xtatishi mumkin.
Masalan:
timeout tanlangach, natijani ishlab chiqarayotgan goroutine avtomatik to‘xtamaydi.
U keyinroq:
qatoriga kelishi mumkin.
Agar channel buffersiz bo‘lsa va boshqa qabul qiluvchi qolmagan bo‘lsa, worker bloklanib qolishi mumkin.
Shu sabab timeoutli kodda worker lifecycle’ini ham o‘ylash kerak.
Ko‘pincha buning uchun:
- bufferli result channel;
context.Context;- alohida cancellation channel
ishlatiladi.
Kanalni qabul qiluvchi tomonda yopish
Channelni kim yopishi ham muhim.
Odatda kanalga boshqa qiymat yuborilmasligini aniq biladigan tomon channelni yopadi. Ko‘pincha bu producer, ya’ni yuboruvchi tomon bo‘ladi.
Masalan:
Bu yerda producer barcha natijalarni yuborganini biladi. Shu sabab resultsni qachon yopish mumkinligini ham biladi.
Consumer esa odatda boshqa producer hali yuboradimi yoki yo‘qmi degan ma’lumotga ega bo‘lmasligi mumkin.
Yopilgan channelga yana qiymat yuborilsa, panic yuz beradi.
Shuning uchun channelni yopish ownership’ini aniq belgilash muhim.
Interviewda muhim nuqtalar
select haqida suhbat yoki interviewda quyidagi qoidalarni aniq ayta olish foydali.
-
selecthar bajarilganda ko‘pi bilan bitta tayyorcaseni tanlaydi. -
defaultsizselectda hech bir operatsiya tayyor bo‘lmasa, joriy goroutine bloklanadi. -
defaultmavjud bo‘lsa va boshqacaselar tayyor bo‘lmasa,defaultdarhol bajariladi. Shu sabab bundayselectbloklanmaydi. -
Bir nechta
casebir vaqtda tayyor bo‘lsa, kodda yuqoriroq yozilgani avtomatik ustun bo‘lmaydi. -
Yopilgan channeldan qabul qilish bloklanmaydi. Buffer tugagach, zero value va
ok == falseolinadi. -
nilchannel esa yuborish va qabul qilish uchun tayyor bo‘lmaydi. Shu sababselectichida channelninilqilish uningcaseini vaqtincha o‘chirib qo‘yish usuli sifatida ishlatilishi mumkin. -
select {}da hech qandaycaseyokidefaultyo‘q. Shu sabab u joriy goroutineni abadiy bloklaydi. -
Timeout faqat natijani kutishni to‘xtatadi. U ishni bajarayotgan goroutineni avtomatik bekor qilmaydi.
-
Agar timeoutdan keyin workerning o‘zini ham to‘xtatish kerak bo‘lsa,
contextyoki alohida cancellation mexanizmi kerak.