golang 個人筆記和心得

goroutine - channels

Don't communicate by sharing memory; share memory by communicating.

不同於過去的 muliti thread 的程式開發, 常使用共用變數去做資訊傳遞 (communicate by sharing memory).

goroutine 彼此的溝通方式是使用 channel (share memory by communicating). 這樣的方式會讓整個流程在表現上變得清楚.

channels

channel 的通訊, 需要一個 sender 和一個 receiver, 當 sender 和 receiver 都 ready 時候, 訊息才會成功的傳遞.

import "fmt"

func main(){
     s := mack(chan string) //宣告一個 channel 變數
     s <- "hello"           //寫入 channel (sender)
     val <- s               //讀取 channel (receiver)
     fmt.Println(val)
}

上例說明了如何宣告 channel 變數, 寫入 channel, 讀取 channel.

但是 code 無法跑, compiler 會顯示 dead lock.

因為執行到 s <- "hello" 這步的時候, sender 就會進入 ready 狀態.

然後就停住了, 也就是他不會執行到 val := <- s .

這時我們建立一個 goroutine 去跑 s <- "hello"

import "fmt"


func main(){
     s := make(chan string) //宣告一個 channel 變數
     go func(){
        s <- "hello"           //寫入 channel (sender)
     }()

     val := <- s               //讀取 channel (receiver)
     fmt.Println(val)
}

執行順序就是

1. 建立一個 goroutine  //此時 s <- "hello" 還沒執行
2. 執行 val := <- s   //s 空的, receiver ready (停住)
3. 執行 s <- "hello"  //sender 將訊息寫入 s, sender ready
4. val := <- s       //成功讀取 s, (因為 receiver, sender 都 ready)
5. fmt.Println(val)

goroutine 不見得會比較慢執行. 不過重點不在這, 就不多描述了.

所以要看執行的順序就要注意這原則 "當 sender 和 receiver 都 ready 時候, 訊息才會成功的傳遞. "

再舉一個例子, 請問順序式如何?

import "fmt"

func main(){
     s := make(chan string)
     go func() {
          for i := 0; i < 3; i++ {
               fmt.Println("sender hello",i)
               s <- fmt.Sprintf("receiver hello %d", i)
          }
     }()

     for i := 0; i < 3; i++ {
          val := <-s
          fmt.Println(val)
     }
}

結果

sender hello 0
sender hello 1
receiver hello 0
receiver hello 1
sender hello 2
receiver hello 2

有對嗎?程式碼順序是這樣

1. 建立一個 goroutine 
2. 執行 val := <- s          //s 空的, receiver ready (停住)
[sender hello 0]
3. 執行 s <- fmt.Sprintf..   //sender 將訊息寫入 s, 傳訊成功
[sender hello 1]
4. 執行 s <- fmt.Sprintf..   //s 有值, sender ready(停住)
5. val := <-s               //讀到 s 的值
[receiver hello 0]
6. val := <-s               //s 空的, receiver ready, 傳訊成功
[receiver hello 1]
7. val := <-s               //s 空的, receiver ready (停住)
[sender hello 2]
8. 執行 s <- fmt.Sprintf..   //sender 將訊息寫入 s, 傳訊成功
9. val := <-s               //讀到 s 的值
[sender hello 2]

另外我們可以修改 channel 的大小為 2

import "fmt"

func main(){
     s := make(chan string,2)
     go func() {
          for i := 0; i < 3; i++ {
               fmt.Println("sender hello",i)
               s <- fmt.Sprintf("receiver hello %d", i)
          }
     }()

     for i := 0; i < 3; i++ {
          val := <-s
          fmt.Println(val)
     }
}

結果

sender hello 0
sender hello 1
sender hello 2
receiver hello 0
receiver hello 1
receiver hello 2
reference

http://guzalexander.com/2013/12/06/golang-channels-tutorial.html