golang 學習 - chan
1. 通道
// _通道_ 是連接多個 Go 協程的管道。你可以從一個 Go 協程
// 將值發送到通道,然後在別的 Go 協程中接收。
package main
import "fmt"
func main() {
// 使用 `make(chan val-type)` 創建一個新的通道。
// 通道類型就是他們需要傳遞值的類型。
messages := make(chan string)
// 使用 `channel <-` 語法 _發送_ 一個新的值到通道中。這裏
// 我們在一個新的 Go 協程中發送 `"ping"` 到上面創建的
// `messages` 通道中。
go func() { messages <- "ping" }()
// 使用 `<-channel` 語法從通道中 _接收_ 一個值。這裏
// 將接收我們在上面發送的 `"ping"` 消息並打印出來。
msg := <-messages
fmt.Println(msg)
}
2. 通道緩存
// 默認通道是 _無緩衝_ 的,這意味着只有在對應的接收(`<- chan`)
// 通道準備好接收時,才允許進行發送(`chan <-`)。_可緩存通道_
// 允許在沒有對應接收方的情況下,緩存限定數量的值。
package main
import "fmt"
func main() {
// 這裏我們 `make` 了一個通道,最多允許緩存 2 個值。
messages := make(chan string, 2)
// 因爲這個通道是有緩衝區的,即使沒有一個對應的併發接收
// 方,我們仍然可以發送這些值。
messages <- "buffered"
messages <- "channel"
// 然後我們可以像前面一樣接收這兩個值。
fmt.Println(<-messages)
fmt.Println(<-messages)
}
3. 通道同步
// 我們可以使用通道來同步 Go 協程間的執行狀態。這裏是一個
// 使用阻塞的接受方式來等待一個 Go 協程的運行結束。
package main
import "fmt"
import "time"
// 這是一個我們將要在 Go 協程中運行的函數。`done` 通道
// 將被用於通知其他 Go 協程這個函數已經工作完畢。
func worker(done chan bool) {
fmt.Print("working...")
time.Sleep(time.Second)
fmt.Println("done")
// 發送一個值來通知我們已經完工啦。
done <- true
}
func main() {
// 運行一個 worker Go協程,並給予用於通知的通道。
done := make(chan bool, 1)
go worker(done)
// 程序將在接收到通道中 worker 發出的通知前一直阻塞。
<-done
}
4. 通道方向
// 當使用通道作爲函數的參數時,你可以指定這個通道是不是
// 只用來發送或者接收值。這個特性提升了程序的類型安全性。
package main
import "fmt"
// `ping` 函數定義了一個只允許發送數據的通道。嘗試使用這個通
// 道來接收數據將會得到一個編譯時錯誤。
func ping(pings chan<- string, msg string) {
pings <- msg
}
// `pong` 函數允許通道(`pings`)來接收數據,另一通道
// (`pongs`)來發送數據。
func pong(pings <-chan string, pongs chan<- string) {
msg := <-pings
pongs <- msg
}
func main() {
pings := make(chan string, 1)
pongs := make(chan string, 1)
ping(pings, "passed message")
pong(pings, pongs)
fmt.Println(<-pongs)
}
5. 通道選擇器
package main
import "time"
import "fmt"
func main() {
// 在我們的例子中,我們將從兩個通道中選擇。
c1 := make(chan string)
c2 := make(chan string)
// 各個通道將在若干時間後接收一個值,這個用來模擬例如
// 並行的 Go 協程中阻塞的 RPC 操作
go func() {
time.Sleep(time.Second * 1)
c1 <- "one"
}()
go func() {
time.Sleep(time.Second * 2)
c2 <- "two"
}()
// 我們使用 `select` 關鍵字來同時等待這兩個值,並打
// 印各自接收到的值。
for i := 0; i < 2; i++ {
select {
case msg1 := <-c1:
fmt.Println("received", msg1)
case msg2 := <-c2:
fmt.Println("received", msg2)
}
}
}
6. 超時處理
package main
import "time"
import "fmt"
func main() {
// 在我們的例子中,假如我們執行一個外部調用,並在 2 秒後
// 通過通道 `c1` 返回它的執行結果。
c1 := make(chan string, 1)
go func() {
time.Sleep(time.Second * 2)
c1 <- "result 1"
}()
// 這裏是使用 `select` 實現一個超時操作。
// `res := <- c1` 等待結果,`<-Time.After` 等待超時
// 時間 1 秒後發送的值。由於 `select` 默認處理第一個
// 已準備好的接收操作,如果這個操作超過了允許的 1 秒
// 的話,將會執行超時 case。
select {
case res := <-c1:
fmt.Println(res)
case <-time.After(time.Second * 1):
fmt.Println("timeout 1")
}
}
7. 定時器
package main
import "time"
import "fmt"
func main() {
// 定時器表示在未來某一時刻的獨立事件。你告訴定時器
// 需要等待的時間,然後它將提供一個用於通知的通道。
// 這裏的定時器將等待 2 秒。
timer1 := time.NewTimer(time.Second * 2)
// `<-timer1.C` 直到這個定時器的通道 `C` 明確的發送了
// 定時器失效的值之前,將一直阻塞。
<-timer1.C
fmt.Println("Timer 1 expired")
// 如果你需要的僅僅是單純的等待,你需要使用 `time.Sleep`。
// 定時器是有用原因之一就是你可以在定時器失效之前,取消這個
// 定時器。這是一個例子
timer2 := time.NewTimer(time.Second)
go func() {
<-timer2.C
fmt.Println("Timer 2 expired")
}()
stop2 := timer2.Stop()
if stop2 {
fmt.Println("Timer 2 stopped")
}
}
8. 打點器
package main
import "time"
import "fmt"
func main() {
// 打點器和定時器的機制有點相似:一個通道用來發送數據。
// 這裏我們在這個通道上使用內置的 `range` 來迭代值每隔
// 500ms 發送一次的值。
ticker := time.NewTicker(time.Millisecond * 500)
go func() {
for t := range ticker.C {
fmt.Println("Tick at", t)
}
}()
// 打點器可以和定時器一樣被停止。一旦一個打點停止了,
// 將不能再從它的通道中接收到值。我們將在運行後 1500ms
// 停止這個打點器。
time.Sleep(time.Millisecond * 1500)
ticker.Stop()
fmt.Println("Ticker stopped")
}
9. 工作池
package main
import "fmt"
import "time"
// 這是我們將要在多個併發實例中支持的任務了。這些執行者
// 將從 `jobs` 通道接收任務,並且通過 `results` 發送對應
// 的結果。我們將讓每個任務間隔 1s 來模仿一個耗時的任務。
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Println("worker", id, "processing job", j)
time.Sleep(time.Second)
results <- j * 2
}
}
func main() {
// 爲了使用 worker 工作池並且收集他們的結果,我們需要
// 2 個通道。
jobs := make(chan int, 100)
results := make(chan int, 100)
// 這裏啓動了 3 個 worker,初始是阻塞的,因爲
// 還沒有傳遞任務。
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
// 這裏我們發送 9 個 `jobs`,然後 `close` 這些通道
// 來表示這些就是所有的任務了。
for j := 1; j <= 9; j++ {
jobs <- j
}
close(jobs)
// 最後,我們收集所有這些任務的返回值。
for a := 1; a <= 9; a++ {
<-results
}
}