61 / 73

Channels

Oldingi bobda biz goroutine'larni qanday ishga tushirishni va ular o'rtasida kanallar orqali ma'lumot uzatishni ko'rib chiqdik. Ammo Go kanallari bundan ancha ko'p va qiziqarli imkoniyatlarga ega. Keling, ularni birma-bir o'rganamiz!

Bob mundarijasi:


Ma'lumot tugaganligini bildirish

Keling, satrni vergullar bo'yicha bo'laklarga ajratadigan va bo'sh qismlarni filtrlab tashlaydigan dasturni ko'rib chiqaylik:

example.go
package main

import (
	"fmt"
	"strings"
)

func main() {
	str := "one,two,,four"

	in := make(chan string)
	go func() { // (1)
		words := strings.Split(str, ",")
		for _, word := range words {
			in <- word
		}
	}()

	for { // (2)
		word := <-in
		if word != "" {
			fmt.Printf("%s ", word)
		}
	}
}

Natija:

text
one two four 
fatal error: all goroutines are asleep - deadlock!

Goroutine ➊ satrni so'zlarga ajratadi va ularni in kanaliga yuboradi. Sikl ➋ esa kanaldan so'zlarni o'qiydi va bo'sh bo'lmaganlarini ekranga chiqaradi.

Afsuski, dastur muvaffaqiyatsiz tugadi:

text
fatal error: all goroutines are asleep - deadlock!

Muammo shundaki, sikl ➋ cheksiz ishlaydi:

example.go
for {
	word := <-in
	if word != "" {
		fmt.Printf("%s ", word)
	}
}

Qayerdan bilish mumkin, in kanalida boshqa so'z qolmaganini va sikldan chiqish vaqti kelganini? Oldingi bobda biz buni bo'sh satr "" orqali tekshirib hal qilgan edik:

example.go
for {
	word := <-in
	if word == "" {
		break
	}
}

Lekin hozirgi misolimizda bo'sh satr — bu to'g'ri, haqiqiy ma'lumot bo'lishi mumkin (chunki "one,two,,four" satrida ikkita vergul orasida bo'sh satr bor). Uni shunchaki tashlab yuborish kerak, sikldan chiqish signali sifatida qabul qilib bo'lmaydi!

Buni hal qilishning bir yo'li — maxsus belgi (sentinel value) yuborish:

  • Goroutine so'zlarni tugatgandan so'ng in kanaliga maxsus qiymat yuboradi ➊
  • Sikl ushbu maxsus qiymatni kutadi va uni ko'rgach ishini to'xtatadi ➋
example.go
package main

import (
	"fmt"
	"strings"
)

const eof = "__EOF__"

func main() {
	str := "one,two,,four"

	in := make(chan string)
	go func() {
		words := strings.Split(str, ",")
		for _, word := range words {
			in <- word
		}
		in <- eof // (1)
	}()

	for {
		word := <-in
		if word == eof { // (2)
			break
		}
		if word != "" {
			fmt.Printf("%s ", word)
		}
	}
	fmt.Println()
}

Natija:

text
one two four

Dastur ishladi, ammo tasavvur qilganingizdek, bu juda zaif va noqulay yechim: agar haqiqiy matn ichida tasodifan "__EOF__" so'zi uchrab qolsa nima bo'ladi? Yaxshiyamki, Go buning uchun maxsus toza mexanizmni taqdim etadi.


Kanalni yopish

Biz concurrent (bir vaqtda ishlovchi) muhitda ikki tomonning o'zaro aloqasidagi klassik muammoga duch keldik:

  • Yozuvchi (writer) kanalga qiymatlarni yuboradi.
  • O'quvchi (reader) kanaldan qiymatlarni qabul qiladi.
  • Yozuvchi o'quvchiga boshqa hech qanday qiymat bo'lmasligini qanday xabar qiladi?

Go tilida bu muammoni hal qiluvchi rasmiy mexanizm bor:

  • Yozuvchi kanalni yopishi (close) mumkin.
  • O'quvchi kanal yopilganligini aniqlay oladi.

Yozuvchi kanalni Go'ning o'rnatilgan close() funksiyasi orqali yopadi:

example.go
str := "one,two,,four"
in := make(chan string)

go func() {
	words := strings.Split(str, ",")
	for _, word := range words {
		in <- word
	}
	close(in) // barcha ma'lumotlar uzatib bo'lindi!
}()

O'quvchi esa kanaldan o'qiyotganda ikkinchi qiymat — "comma OK" orqali kanal holatini tekshiradi:

example.go
for {
	word, ok := <-in
	if !ok {
		// kanal yopilgan va bo'sh!
		break
	}
	if word != "" {
		fmt.Printf("%s ", word)
	}
}

Natija:

text
one two four

Faraz qilaylik, yozuvchi "one" va "two" satrlarini yubordi va so'ng kanalni yopdi. O'quvchi nimani oladi:

example.go
// in <- "one"
// in <- "two"
// close(in)

val, ok := <-in // val: "one", ok: true
val, ok := <-in // val: "two", ok: true
val, ok := <-in // val: "", ok: false
val, ok := <-in // val: "", ok: false

Kanal ochiq bo'lsa, o'quvchi navbatdagi qiymatni va true statusini oladi. Agar kanal yopilgan bo'lsa, o'quvchi tipning nol qiymatini (satrlar uchun "", sonlar uchun 0, pointerlar uchun nil) va false statusini oladi.

Ko'rib turganingizdek, yopilgan kanaldan istalgancha ko'p marta o'qish mumkin — u har doim nol qiymat va false qaytaradi. Bu tasodifiy emas, balki Go tilining qasddan shunday loyihalangan muhim xususiyatidir.

[!WARNING] Kanal faqat bir marta yopilishi kerak. Yopilgan kanalni yana qayta yopish (yoki nil kanalni yopish) panic keltirib chiqaradi:

example.go
in := make(chan string)
close(in)
close(in) // panic: close of closed channel!

Kanal bo'ylab iteratsiya

for sikli va ok statusi orqali tekshirish juda yaxshi ishlaydi, ammo Go tilida yopiq kanallarni o'qish uchun yanada ixcham va qulay sintaksis mavjud — bu range operatoridir:

example.go
package main

import (
	"fmt"
	"strings"
)

func main() {
	str := "one,two,,four"

	in := make(chan string)
	go func() {
		words := strings.Split(str, ",")
		for _, word := range words {
			in <- word
		}
		close(in)
	}()

	for word := range in {
		if word != "" {
			fmt.Printf("%s ", word)
		}
	}
	fmt.Println()
}

for word := range in sikli kanaldan qiymatlarni bittalab o'qiydi va kanal yopilgach, siklni o'z-o'zidan xavfsiz to'xtatadi. Bu aynan avvalgi word, ok := <-in; if !ok { break } kodi bilan bir xil ishlaydi, biroq ancha toza va o'qilishi oson!

[!IMPORTANT] range orqali kanaldan o'qiyotganingizda, yozuvchi goroutine kanalni albatta close() qilishi SHART! Aks holda qabul qiluvchi sikl oxirgi qiymatdan keyin yangi qiymat kutib abadiy bloklanadi va dastur deadlock holatiga tushadi.


Topshiriq 1: Range va kanalni yopish

Berilgan sonlar massividagi faqat juft sonlarni alohida goroutine orqali kanalga chiqaruvchi generateEvens(nums []int) <-chan int funksiyasini yozing.

  • Funksiya ichida goroutine ishga tushirilsin, juft sonlar kanalga yozilsin va oxirida kanal yopilsin (close).
  • Chiqish kanali range orqali o'qilganda barcha juft sonlar to'g'ri chiqsin va dastur deadlock'siz yakunlansin.
example.go
package main

import "fmt"

// generateEvens faqat juft sonlarni kanal orqali yuboradi va kanalni yopadi.
func generateEvens(nums []int) <-chan int {
	out := make(chan int)
	// TODO: goroutine ishga tushiring, juft sonlarni out kanaliga yuboring va kanalni yoping
	return out
}

func main() {
	numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	for n := range generateEvens(numbers) {
		fmt.Printf("%d ", n)
	}
	fmt.Println()
}

Yo'naltirilgan kanallar

Keling, oldingi dasturimizni alohida filter funksiyasini ajratib olgan holda qayta tuzaylik:

example.go
// filter in kanalidan so'zlarni o'qiydi, bo'sh bo'lmaganlarini out kanaliga yuboradi
func filter(in chan string, out chan string) {
	for word := range in {
		if word != "" {
			out <- word
		}
	}
	close(out)
}

Bu yerda filter funksiyasi ikkita kanal qabul qiladi:

  • in — faqat o'qish uchun;
  • out — faqat yozish uchun.

Ammo hozirgi ko'rinishida har ikkala kanal ikki tomonlama (bidirectional) hisoblanadi. Agar dasturchi yanglishib in kanaliga yozib qo'ysa yoki out kanalidan o'qishga harakat qilsa, Go kompilyatori bunga to'sqinlik qilolmaydi.

Bunday xatolarning oldini olish uchun Go yo'naltirilgan kanallar (directional channels) turini taqdim etadi:

  • chan<- T — faqat yozish uchun (send-only channel);
  • <-chan T — faqat o'qish uchun (receive-only channel).

Strelka <- yozuviga qarab eslab qolish juda oson:

  • chan<- — qiymat kanalga kirib ketmoqda (yozish);
  • <-chan — qiymat kanaldan chiqib kelmoqda (o'qish).

Keling, filter parametrlarini to'g'ri yo'nalishlar bilan belgilaymiz:

example.go
func filter(in <-chan string, out chan<- string) {
	for word := range in {
		if word != "" {
			out <- word
		}
	}
	close(out)
}

Endi agar siz adashib in <- "xato" deb yozsangiz, Go kompilyatori kodni ishga tushirishdan oldin to'xtatadi:

text
invalid operation: cannot send to receive-only channel in (type <-chan string)

Xuddi shuningdek, close() funksiyasini faqat yozish huquqiga ega bo'lgan (chan<-) yoki ikki tomonlama (chan) kanallarda chaqirish mumkin. Qabul qiluvchi (<-chan) kanalni yopishga urinish ham kompilyatsiya xatosidir.

Oddiy ikki tomonlama kanal chan T avtomatik ravishda chan<- T yoki <-chan T turiga o'tadi (implicit conversion). Shuning uchun funksiyalar chaqiruvida hech qanday ortiqcha cast qilish shart emas.


Topshiriq 2: Yo'naltirilgan kanallar bilan filtrlash

Sonlarni kvadratga ko'taruvchi square(in <-chan int, out chan<- int) funksiyasini yozing. U in kanalidan barcha sonlarni o'qib, har birining kvadratini out kanaliga yuborishi va ish yakunida out kanalini yopishi kerak.

example.go
package main

import "fmt"

func square(in <-chan int, out chan<- int) {
	// TODO: in kanalidan range orqali o'qing, kvadratini out ga yozing va out ni yoping
}

func main() {
	in := make(chan int)
	out := make(chan int)

	go func() {
		defer close(in)
		for i := 1; i <= 5; i++ {
			in <- i
		}
	}()

	go square(in, out)

	for res := range out {
		fmt.Printf("%d ", res)
	}
	fmt.Println()
}
example.go
package main

import "fmt"

func square(in <-chan int, out chan<- int) {
	defer close(out)
	for num := range in {
		out <- num * num
	}
}

func main() {
	in := make(chan int)
	out := make(chan int)

	go func() {
		defer close(in)
		for i := 1; i <= 5; i++ {
			in <- i
		}
	}()

	go square(in, out)

	for res := range out {
		fmt.Printf("%d ", res)
	}
	fmt.Println()
}

Done kanali

Tasavvur qiling, siz bir vaqtda ishlayotgan bir nechta goroutine'larni boshqaryapsiz va ularning barchasiga "Ishni to'xtating!" degan umumiy signal yuborishingiz kerak.

Buni qanday amalga oshirish mumkin? Kanalga qiymat yuborish faqat bitta qabul qiluvchini uyg'otadi. Agar sizda 10 ta goroutine bo'lsa, siz kanalga 10 marta qiymat yuborishingiz kerak bo'ladi.

Lekin biz yuqorida ajoyib xususiyatni o'rgandik: yopilgan kanaldan istalgancha ko'p marta o'qish mumkin va u darhol javob qaytaradi!

Demak, agar biz kanalni close(done) qilsak, ushbu kanalni kutayotgan barcha goroutine'lar bir vaqtning o'zida blokirovkadan chiqadi va to'xtash signali oladi!

Bunday kanal odatda done channel (bajarildi kanali) deb ataladi va uning tipi sifatida chan struct{} tanlanadi, chunki Go tilida bo'sh struct struct{} xotirada 0 bayt joy egallaydi:

example.go
package main

import (
	"fmt"
	"time"
)

func worker(id int, done <-chan struct{}) {
	for {
		select {
		case <-done:
			fmt.Printf("Worker #%d: to'xtash signali olindi
", id)
			return
		default:
			// Foydali ish bajarish simulyatsiyasi:
			time.Sleep(50 * time.Millisecond)
		}
	}
}

func main() {
	done := make(chan struct{})

	// 3 ta ishchini ishga tushiramiz:
	for i := 1; i <= 3; i++ {
		go worker(i, done)
	}

	time.Sleep(120 * time.Millisecond)
	fmt.Println("Main: barcha ishchilarni to'xtatish signali yuborilmoqda...")
	close(done) // hamma ishchilar bir vaqtda signal oladi!

	time.Sleep(50 * time.Millisecond)
}

Topshiriq 3: Done channel orqali to'xtatish signali

Quyidagi mashqda berilgan soniyadan keyin close(done) chaqirib, cheksiz hisoblab turgan ishchini xavfsiz to'xtating.

example.go
package main

import (
	"fmt"
	"time"
)

func counter(done <-chan struct{}) <-chan int {
	out := make(chan int)
	go func() {
		defer close(out)
		n := 1
		for {
			select {
			case <-done:
				return
			case out <- n:
				n++
				time.Sleep(20 * time.Millisecond)
			}
		}
	}()
	return out
}

func main() {
	done := make(chan struct{})
	ch := counter(done)

	// TODO: 5 ta son olgach done kanalini yoping
	for n := range ch {
		fmt.Printf("%d ", n)
		if n == 5 {
			// done kanalini yoping
		}
	}
	fmt.Println("
Tugatildi!")
}
example.go
package main

import (
	"fmt"
	"time"
)

func counter(done <-chan struct{}) <-chan int {
	out := make(chan int)
	go func() {
		defer close(out)
		n := 1
		for {
			select {
			case <-done:
				return
			case out <- n:
				n++
				time.Sleep(20 * time.Millisecond)
			}
		}
	}()
	return out
}

func main() {
	done := make(chan struct{})
	ch := counter(done)

	for n := range ch {
		fmt.Printf("%d ", n)
		if n == 5 {
			close(done)
		}
	}
	fmt.Println("
Tugatildi!")
}

Deadlock'larning oldini olish

Go tilida buferlanmagan kanallar bilan ishlaganda eng ko'p uchraydigan xato — bu deadlock (boshi berk ko'cha)dir:

example.go
func main() {
	ch := make(chan int)
	ch <- 42 // DEADLOCK!
	fmt.Println(<-ch)
}

Bu dastur nima uchun ishlamaydi? Chunki buferlanmagan kanal — bu sinxron uchrashuv nuqtasi (rendezvous). Yuboruvchi ma'lumotni yozish paytida qarama-qarshi tomonda uni qabul qilib oluvchi boshqa goroutine hozir bo'lishini talab qiladi. Bir xil goroutine ichida avval yozib, keyin o'qiyman deyish imkonsiz — birinchi qatordayoq goroutine muzlaydi va dastur halokatga uchraydi!

Deadlock'dan qochish qoidalari:

  1. Kanalga yozuvchi va o'quvchi kamida ikkita alohida goroutine'da bo'lishi kerak.
  2. Agar qabul qiluvchi hali tayyor bo'lmasdan turib ma'lumot yozish kerak bo'lsa, buferlangan kanallardan foydalanish lozim.
  3. Hech qachon goroutine o'qimay tashlab ketadigan kanallarga cheksiz yozmang (goroutine leak xavfi).

Buferlangan kanallar

Buferlanmagan kanalda ch <- x amali qabul qiluvchi kelguncha darhol bloklanadi. Ammo agar biz kanal yaratayotganda unga sig'im (capacity) ko'rsatsak:

example.go
ch := make(chan int, 3)

Bu buferlangan kanal (buffered channel) bo'ladi!

kod
┌─────────────────────────────────┐
│ Bufer: [ 10 ] [ 20 ] [ 30 ]     │  Sig'imi: 3 ta element
└─────────────────────────────────┘

Uning ishlash qoidalari:

  • Buferda bo'sh joy bor ekan, yuboruvchi ch <- x qilganda bloklanmaydi — qiymat buferga qo'yiladi va yuboruvchi o'z ishida davom etadi.
  • Faqatgina bufer to'lganda (len(ch) == cap(ch)) navbatdagi yuboruvchi bloklanadi.
  • Buferda kamida bitta element bor ekan, o'quvchi <-ch qilganda bloklanmaydi — buferdagi eng birinchi elementni (FIFO) oladi.
  • Faqatgina bufer bo'sh bo'lganda o'quvchi bloklanadi.
example.go
package main

import "fmt"

func main() {
	ch := make(chan string, 2) // 2 ta elementli bufer

	ch <- "salom"
	ch <- "dunyo"

	// Bufer to'ldi, ammo biz hech qanday alohida goroutine ochmadik
	// chunki bufer yozuvchini bloklamadi!

	fmt.Println(<-ch) // salom
	fmt.Println(<-ch) // dunyo
}

Topshiriq 4: Promise.all() andozasi

JavaScript'dagi Promise.all() kabi, bir nechta mustaqil asinxron operatsiyalarni parallel ishga tushirib, ularning natijalarini buferlangan kanal orqali yig'uvchi funksiya tuzing.

example.go
package main

import (
	"fmt"
	"time"
)

func fetchSource(id int, ch chan<- string) {
	time.Sleep(50 * time.Millisecond)
	ch <- fmt.Sprintf("Manba #%d dan ma'lumot", id)
}

func main() {
	sources := 3
	results := make(chan string, sources)

	// TODO: 3 ta manba uchun goroutine ishga tushiring va natijalarni results ga yuboring
	for i := 1; i <= sources; i++ {
		// go fetchSource(...)
	}

	// TODO: results kanalidan 3 ta natijani o'qib ekranga chiqaring
}
example.go
package main

import (
	"fmt"
	"time"
)

func fetchSource(id int, ch chan<- string) {
	time.Sleep(50 * time.Millisecond)
	ch <- fmt.Sprintf("Manba #%d dan ma'lumot", id)
}

func main() {
	sources := 3
	results := make(chan string, sources)

	for i := 1; i <= sources; i++ {
		go fetchSource(i, results)
	}

	for i := 1; i <= sources; i++ {
		fmt.Println(<-results)
	}
}

async / await andozasi

Boshqa dasturlash tillarida (JS, C#, Python) keng tarqalgan async/await andozasini Go tilida kanallar orqali juda tabiiy va qulay amalga oshirish mumkin:

  • async funksiya — bu ichida goroutine ishga tushirib, natija uchun kanal qaytaruvchi oddiy funksiya.
  • await — bu o'sha kanaldan qiymat o'qish (<-ch).
example.go
package main

import (
	"fmt"
	"time"
)

// downloadAsync ma'lumotni orqa fonda yuklaydi va kanal qaytaradi (async)
func downloadAsync(url string) <-chan string {
	out := make(chan string, 1)
	go func() {
		time.Sleep(100 * time.Millisecond) // yuklash simulyatsiyasi
		out <- "Ma'lumot: " + url
	}()
	return out
}

func main() {
	// Async tarzda ikkita yuklashni boshlaymiz:
	future1 := downloadAsync("https://golang.org")
	future2 := downloadAsync("https://pkg.go.dev")

	// Asosiy oqim boshqa ishlar bilan band bo'lishi mumkin...
	fmt.Println("Boshqa hisob-kitoblar bajarilmoqda...")

	// Endi natijalarni kutamiz (await):
	res1 := <-future1
	res2 := <-future2

	fmt.Println(res1)
	fmt.Println(res2)
}

Semafor (Semaphore)

Tizim resurslarini (masalan, ma'lumotlar bazasi ulanishlari sonini yoki parallel yuborilayotgan HTTP so'rovlar sonini) asrash uchun bir vaqtning o'zida ko'pi bilan $N$ ta goroutine ishlashiga ruxsat beruvchi mexanizm kerak bo'ladi. Bu semafor deb ataladi.

Go tilida buferlangan kanal tayyor semafor vazifasini o'taydi:

  • sem := make(chan struct{}, N) — maksimal $N$ ta ruxsat biletiga ega semafor.
  • sem <- struct{}{} — bilet olish (agar bufer to'lgan bo'lsa, boshqa ishchi chiqib ketguncha kutadi).
  • <-sem — biletni qaytarib topshirish (bo'shatish).
example.go
package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	const maxConcurrent = 2 // bir vaqtda ko'pi bilan 2 ta goroutine
	sem := make(chan struct{}, maxConcurrent)
	var wg sync.WaitGroup

	for i := 1; i <= 5; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()

			sem <- struct{}{} // ruxsat olish
			defer func() { <-sem }() // ish tugagach ruxsatni bo'shatish

			fmt.Printf("Vazifa #%d boshlandi
", id)
			time.Sleep(100 * time.Millisecond)
			fmt.Printf("Vazifa #%d tugatildi
", id)
		}(i)
	}

	wg.Wait()
}

Topshiriq 5: Cheklangan ishchilar puli

5 ta veb-sahifani parallel yuklash kerak, ammo serverni yuklab qo'ymaslik uchun bir vaqtda faqat 2 ta yuklovchi ishlashi mumkin. Yuqoridagi semafor andozasidan foydalanib dasturni to'ldiring.

example.go
package main

import (
	"fmt"
	"sync"
	"time"
)

func download(url string, sem chan struct{}, wg *sync.WaitGroup) {
	// TODO: semafor orqali cheklov o'rnating, URLni ekranga chiqaring va ruxsatni bo'shating
}

func main() {
	urls := []string{"site1.uz", "site2.uz", "site3.uz", "site4.uz", "site5.uz"}
	sem := make(chan struct{}, 2)
	var wg sync.WaitGroup

	for _, u := range urls {
		wg.Add(1)
		go download(u, sem, &wg)
	}

	wg.Wait()
	fmt.Println("Barcha sahifalar yuklandi!")
}
example.go
package main

import (
	"fmt"
	"sync"
	"time"
)

func download(url string, sem chan struct{}, wg *sync.WaitGroup) {
	defer wg.Done()
	sem <- struct{}{}        // bilet olish
	defer func() { <-sem }() // biletni qaytarish

	fmt.Printf("Yuklanmoqda: %s
", url)
	time.Sleep(50 * time.Millisecond)
}

func main() {
	urls := []string{"site1.uz", "site2.uz", "site3.uz", "site4.uz", "site5.uz"}
	sem := make(chan struct{}, 2)
	var wg sync.WaitGroup

	for _, u := range urls {
		wg.Add(1)
		go download(u, sem, &wg)
	}

	wg.Wait()
	fmt.Println("Barcha sahifalar yuklandi!")
}

Buferlangan kanalni yopish

Buferlangan kanal yopilganda nima yuz beradi? Go juda mantiqiy qoidaga amal qiladi:

  1. Kanal yopilgandan so'ng unga yangi element yozib bo'lmaydi (panic: send on closed channel).
  2. Ammo buferda qolgan mavjud elementlar yo'qolib ketmaydi! O'quvchi avval buferdagi barcha qolgan elementlarni ok == true bilan navbatma-navbat o'qib tugatadi.
  3. Faqatgina bufer butunlay bo'shab qolgandagina navbatdagi o'qishda ok == false qiymati qaytariladi.
example.go
package main

import "fmt"

func main() {
	ch := make(chan int, 3)
	ch <- 10
	ch <- 20
	close(ch) // kanal yopildi, lekin buferda 10 va 20 bor

	val, ok := <-ch
	fmt.Printf("val: %d, ok: %t
", val, ok) // 10, true

	val, ok = <-ch
	fmt.Printf("val: %d, ok: %t
", val, ok) // 20, true

	val, ok = <-ch
	fmt.Printf("val: %d, ok: %t
", val, ok) // 0, false (bufer bo'shadi!)
}

nil kanal (nil channel)

Initsializatsiya qilinmagan (ya'ni make chaqirilmagan) kanal nil qiymatiga ega bo'ladi:

example.go
var ch chan int // ch == nil

nil kanal bilan ishlaganda quyidagi g'aroyib, ammo foydali qoidalar amal qiladi:

  1. nil kanalga yozish (ch <- 1) — abadiy bloklaydi!
  2. nil kanaldan o'qish (<-ch) — abadiy bloklaydi!
  3. nil kanalni yopish (close(ch)) — darhol panic beradi!

Bu nimaga kerak? select operatori bilan ishlaganda, vaqtincha biror kanal bo'yicha tinglashni yoki yozishni to'xtatib qo'yish (disable qilish) kerak bo'lsa, ushbu kanal o'zgaruvchisiga shunchaki nil qiymati beriladi. select operatori nil kanallarni o'z-o'zidan e'tiborsiz qoldiradi (ular hech qachon faollashmaydi). Bu juda ilg'or va qulay usuldir!


Xulosa va keyingi qadamlar

Ushbu bobda biz Go kanallarining deyarli barcha jihatlarini to'liq o'rganib chiqdik:

  • close() va ok tekshiruvi orqali ma'lumot yakunlanganini xavfsiz bildirish;
  • for ... range yordamida kanal bo'ylab avtomatik va toza iteratsiya;
  • chan<- va <-chan yo'naltirilgan kanallari orqali kod xavfsizligini ta'minlash;
  • done kanali orqali ko'plab goroutine'larga bir vaqtda to'xtatish signali yuborish;
  • Buferlangan kanallar bilan ishlash, asinxronlik va semafor orqali cheklov o'rnatish;
  • nil kanallarning o'ziga xos xususiyatlari.

Keyingi bob: 03. Pipelines (Kanallar konveyeri)!