[Golang] Channel trong golang và use case – part I

Mở đầu Lập trình concurrency luôn là một thứ làm các gopher tự hào so với các ngôn ngữ khác. Các ngôn ngữ phổ biến như JAVA, C# implements concurrency bằng việc sử dụng thread, còn GO thì sử dụng 2 built-in features – goroutine và channel. Chúng giúp việc tiếp cận hay quản lý

Mở đầu

  • Lập trình concurrency luôn là một thứ làm các gopher tự hào so với các ngôn ngữ khác. Các ngôn ngữ phổ biến như JAVA, C# implements concurrency bằng việc sử dụng thread, còn GO thì sử dụng 2 built-in features – goroutine và channel. Chúng giúp việc tiếp cận hay quản lý các công việc đồng thời trở nên dể dàng và đỡ tốn chi phí hơn rất nhiều.
  • Goroutines bản chất là các hàm (function) hay method được thực thi một các độc lập và đồng thời nhưng vẫn có thể kết nối với nhau. Và channel chính là kênh giao tiếp giữa các goroutine đó.

Channel

  • Channel cung cấp một cách để các goroutines giao tiếp với nhau và đồng bộ hóa việc thực thi của chúng.
  • Nói đến giao tiếp, thì phải có 2 chiều – chiều gửi và chiều nhận. Channel cũng như thế, goroutine hoàn toàn có thể gửi đến dữ liệu đến channel và lấy dữ liệu từ channel.
  • Channel được phân thành 2 loại: buffer channel and unbuffered channel. Đối với unbuffer channel, khi một goroutine A gửi dữ liệu đến thì nó sẽ tiến hành block goroutine A lại cho đến khi có go routine bất kỳ đến lấy dữ liệu. Và ngược lại, buffer channel sẽ nhẹ nhàng hơn, không có yêu cầu khắt khe gì cả, nó được mang trong mình một sức chứa(capicity) nhất định và không cần bất kỳ receiver goroutine nào. Tuy nhiên, khi capicity đến mức giới hạn thì nó cũng cần goroutines đến lấy dữ liệu ra.

Play with unbuffered channel

  • From main goroutine, gửi dữ liệu đến channel without receiver goroutine
    func main() {
       /* Without receiver goroutine from main goroutine */
       chanelAtMainRoutine := make(chan int)
       chanelAtMainRoutine <- 5
    }
    result:
       fatal error: all goroutines are asleep - deadlock!
    

    Chương trình bị crash(terminated) và thông báo lỗi bởi vì main goroutine bị block trong một khoảng thời gian.

  • From anonymous goroutine, gửi dữ liệu đến channel without receiver goroutine
    func main() {
        /* Without receiver from anonymous goroutine */
        chanelAtMainRoutine := make(chan int)
        go func() {
            chanelAtMainRoutine <- 5
        }()
        fmt.Println("Completing ...")
    }
    result:
        Completing ...
    

    Việc chương trình ko bị terminated or crash ở đây là hoàn toàn chính xác mặc dù channel vẫn chưa có một goroutine nào đến lấy dữ liệu. Channel thực chất chỉ block anonymous goroutine và main goroutine vẫn tiếp chạy và in ra kết quả “completing ….”

  • From main goroutine, gửi dữ liệu đến channel with receiver goroutine
    func main() {
        /* With receiver from main goroutine */
        chanelAtMainRoutine := make(chan int)
        go func() {
            fmt.Println("Start getting value from channel ...")
            <-chanelAtMainRoutine
            fmt.Println("Complete getting value from channel ...")
        }()
        chanelAtMainRoutine <- 5
        fmt.Println("Completing ...")
    }
    result:
        Start getting value from channel ...
        Complete getting value from channel ...
        Completing ...
    

    Recevier goroutine xuất hiện và main goroutine ko bị lock nữa. Tuy nhiên, nếu trong quá trình chạy func main nhiều lần ta sẽ thấy dòng “Complete getting value from channel …” sẽ có lúc ko được in ra màn hình console bởi vì ngay tại thời điểm câu lệnh “<-chanelAtMainRoutine” được thực thi xong, thưc chất golang runtime schedule switch sang main goroutine và nếu việc chuyển đổi này đủ nhanh, sẽ ko có câu lệnh nào kịp thực thi ngay sau đó. Để kiểm chứng điều này, tôi sẽ tiến hành sleep một giây tai recevier goroutine.
    Increase time at recevier goroutine

    func main() {
        /* Increase time at receiver goroutine */
        chanelAtMainRoutine := make(chan int)
        go doBigJob(chanelAtMainRoutine)
        chanelAtMainRoutine <- 5
        fmt.Println("Completing ...")
    }
    
    func doBigJob(ch <-chan int) {
        fmt.Println("Start getting value from channel ...")
        <-ch
        time.Sleep(time.Second * 1)
        fmt.Println("Complete getting value from channel ...")
    }
    result:
        Start getting value from channel ... // Cannot see "Complete getting value ..." any more
        Completing ...
    

Tạm kết

  • Về nguyên lý , unbuffered channel hoạt động giống như môn bóng bàn. Khi bóng giao đi thì phải có người đỡ. Ở ví dụ trên chúng ta có thể chuyển sang dùng buffer channel , để câu lệnh fmt.Println(“Complete getting value from channel …”) được thực thi ngay khi dữ liệu của channel được lấy ra.
  • Source code
  • Hẹn các bạn đón xem phần tiếp theo. Chúng ta sẽ đi tìm hiểu thêm về buffered channel và các usecase của nó.

Nguồn: viblo.asia

Bài viết liên quan

WebP là gì? Hướng dẫn cách để chuyển hình ảnh jpg, png qua webp

WebP là gì? WebP là một định dạng ảnh hiện đại, được phát triển bởi Google

Điểm khác biệt giữa IPv4 và IPv6 là gì?

IPv4 và IPv6 là hai phiên bản của hệ thống địa chỉ Giao thức Internet (IP). IP l

Check nameservers của tên miền xem website trỏ đúng chưa

Tìm hiểu cách check nameservers của tên miền để xác định tên miền đó đang dùn

Mình đang dùng Google Domains để check tên miền hàng ngày

Từ khi thông báo dịch vụ Google Domains bỏ mác Beta, mình mới để ý và bắt đầ