Go / select хуваарилагч

select хуваарилагч

Нэг горутин олон channel-ийг нэгэн зэрэг сонсох шаардлага байнга гардаг. Жишээ нь: өгөгдөл хүлээж байхдаа timeout-ийг ч шалгах хэрэгтэй. select нь яг энэ зорилгоор бий болсон.

select үндэс

select нь switch-тэй төстэй боловч channel үйлдлүүдэд ажилладаг. Бэлэн болсон channel-ийн case-ийг гүйцэтгэнэ:

go
package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        time.Sleep(100 * time.Millisecond)
        ch1 <- "ch1-ээс мессеж"
    }()

    go func() {
        time.Sleep(200 * time.Millisecond)
        ch2 <- "ch2-ээс мессеж"
    }()

    // Хоёр channel-ийг нэгэн зэрэг сонсох
    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-ch1:
            fmt.Println("ch1:", msg1)
        case msg2 := <-ch2:
            fmt.Println("ch2:", msg2)
        }
    }
}

select нь бэлэн болсон case руу шилждэг. Хоёр case нэгэн зэрэг бэлэн болвол санамсаргүйгээр нэгийг сонгоно.

Timeout хэрэгжүүлэх

time.After ашиглан хугацаа хэтэрсэн тохиолдлыг зохицуулах:

go
package main

import (
    "fmt"
    "time"
)

func fetchData(ch chan<- string) {
    // Удаан ажиллах процессийг дуурайж байна
    time.Sleep(2 * time.Second)
    ch <- "сервераас ирсэн өгөгдөл"
}

func main() {
    ch := make(chan string)
    go fetchData(ch)

    select {
    case data := <-ch:
        fmt.Println("Хүлээн авсан:", data)
    case <-time.After(1 * time.Second):
        fmt.Println("Хугацаа хэтэрлээ — timeout!")
    }
}

time.After(1 * time.Second) нь 1 секундын дараа утга илгээх channel буцаадаг. fetchData 2 секунд болдог тул timeout case ялна.

default case — блоклохгүй шалгалт

default case нь бусад channel бэлэн болоогүй үед нэн даруй ажилладаг:

go
package main

import "fmt"

func main() {
    ch := make(chan int, 1)

    // Channel дүүрэн үү шалгах
    select {
    case ch <- 10:
        fmt.Println("Утга илгээлээ")
    default:
        fmt.Println("Channel дүүрэн байна, илгээж чадсангүй")
    }

    // Хүлээн авах, хоосон үед skip хийх
    select {
    case v := <-ch:
        fmt.Println("Утга авлаа:", v)
    default:
        fmt.Println("Channel хоосон байна")
    }

    select {
    case v := <-ch:
        fmt.Println("Утга авлаа:", v)
    default:
        fmt.Println("Channel хоосон байна")
    }
}

default байгаа тул select хэзээ ч хаагдахгүй — бэлэн channel байхгүй бол default ажиллана.

Горутин зогсоох — done channel

Горутиныг гадаасаа зогсоох нийтлэг хэв маяг:

go
package main

import (
    "fmt"
    "time"
)

func worker(done <-chan struct{}) {
    for {
        select {
        case <-done:
            fmt.Println("Ажилтан зогслоо")
            return
        default:
            fmt.Println("Ажилтан ажиллаж байна...")
            time.Sleep(300 * time.Millisecond)
        }
    }
}

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

    go worker(done)

    time.Sleep(1 * time.Second)
    close(done) // горутиныг зогсооно
    time.Sleep(100 * time.Millisecond)
    fmt.Println("Дууслаа")
}

chan struct{} нь утга дамжуулах шаардлагагүй, зөвхөн дохио илгээхэд ашигладаг хамгийн хямд channel юм. close(done) нь бүх хүлээн авагчид нэгэн зэрэг дохио илгээдэг.

select дотор олон үйлдэл

go
package main

import (
    "fmt"
    "time"
)

func main() {
    ticker := time.NewTicker(200 * time.Millisecond)
    done := make(chan bool)

    go func() {
        time.Sleep(1 * time.Second)
        done <- true
    }()

    count := 0
    for {
        select {
        case <-done:
            fmt.Printf("Нийт %d удаа тиклэв\n", count)
            ticker.Stop()
            return
        case t := <-ticker.C:
            count++
            fmt.Println("Тик:", t.Format("15:04:05.000"))
        }
    }
}

time.NewTicker нь тогтмол хугацааны зайтай дохио илгээдэг channel юм. ticker.Stop() дуусгахдаа дуудна.

Дараагийн хичээлд:

Горутинүүдийг аюулгүйгээр нэгтгэх sync.WaitGroup болон хуваалцсан өгөгдлийг хамгаалах sync.Mutex-ийг судална.