Go / Channel дэвшилтэт хэрэглээ

Channel дэвшилтэт хэрэглээ

Өмнөх хичээлд unbuffered channel-ийн үндсийг сурсан. Одоо илүү хүчирхэг хэв маягуудыг судална — buffered channel, fan-out, worker pool. Эдгээр нь бодит backend системд байнга ашиглагддаг хэв маягууд юм.

Buffered Channel

Энгийн (unbuffered) channel-д илгээгч хүлээн авагчийг хүлээдэг. Buffered channel нь тодорхой хэмжээний утгыг хуримтлуулж чаддаг тул илгээгч нэн даруй хаагдахгүй:

go
package main

import "fmt"

func main() {
    // 3 утга багтах buffered channel
    ch := make(chan string, 3)

    // Горутингүйгээр шууд илгээх боломжтой
    ch <- "нэг"
    ch <- "хоёр"
    ch <- "гурав"

    fmt.Println(<-ch) // нэг
    fmt.Println(<-ch) // хоёр
    fmt.Println(<-ch) // гурав
}

Buffer дүүрсэн үед илгээгч дахин хүлээнэ. Buffer хоосон үед хүлээн авагч хүлээнэ.

Fan-out хэв маяг

Нэг channel-аас олон горутин утга авах — ажлыг хуваарилах хамгийн энгийн арга:

go
package main

import (
    "fmt"
    "sync"
)

func worker(id int, jobs <-chan int, wg *sync.WaitGroup) {
    defer wg.Done()
    for job := range jobs {
        fmt.Printf("Ажилтан %d: ажил %d-г гүйцэтгэж байна\n", id, job)
    }
}

func main() {
    jobs := make(chan int, 10)
    var wg sync.WaitGroup

    // 3 ажилтан горутин ажиллуулах
    for i := 1; i <= 3; i++ {
        wg.Add(1)
        go worker(i, jobs, &wg)
    }

    // 9 ажил илгээх
    for j := 1; j <= 9; j++ {
        jobs <- j
    }
    close(jobs)

    wg.Wait()
    fmt.Println("Бүх ажил дууслаа")
}

jobs channel-аас гурван ажилтан өрсөлдөн утга авна. Runtime хэн нь түрүүлж чөлөөлөгдсөний дагуу хуваарилна.

Fan-in хэв маяг

Олон channel-ийн өгөгдлийг нэгт нийлүүлэх:

go
package main

import (
    "fmt"
    "sync"
)

func generate(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        for _, n := range nums {
            out <- n
        }
        close(out)
    }()
    return out
}

func merge(channels ...<-chan int) <-chan int {
    merged := make(chan int)
    var wg sync.WaitGroup

    forward := func(ch <-chan int) {
        defer wg.Done()
        for v := range ch {
            merged <- v
        }
    }

    wg.Add(len(channels))
    for _, ch := range channels {
        go forward(ch)
    }

    go func() {
        wg.Wait()
        close(merged)
    }()

    return merged
}

func main() {
    ch1 := generate(1, 3, 5)
    ch2 := generate(2, 4, 6)

    for v := range merge(ch1, ch2) {
        fmt.Println(v)
    }
}

Pipeline хэв маяг

Нэг горутиний гаралт нь нөгөөний оролт болдог дамжуулах хоолой:

go
package main

import "fmt"

func double(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        for v := range in {
            out <- v * 2
        }
        close(out)
    }()
    return out
}

func addTen(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        for v := range in {
            out <- v + 10
        }
        close(out)
    }()
    return out
}

func main() {
    // Эх өгөгдөл
    source := make(chan int)
    go func() {
        for _, v := range []int{1, 2, 3, 4, 5} {
            source <- v
        }
        close(source)
    }()

    // Pipeline: source → double → addTen
    result := addTen(double(source))

    for v := range result {
        fmt.Println(v) // (1*2)+10=12, (2*2)+10=14, ...
    }
}

Pipeline нь Unix-ийн | оператортай адил — нэг процессын гаралт нөгөөний оролт болно.

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

Олон channel-ийг нэгэн зэрэг сонсох select хуваарилагчийг судална. select нь Go-ийн concurrency-ийн маш хүчирхэг хэрэгсэл юм.