golang 學習 - chan以及chan的一下用例

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
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章