官术网_书友最值得收藏!

1.5.6 基于通道的通信

通道(channel)是在goroutine之間進行同步的主要方法。在無緩存的通道上的每一次發送操作都有與其對應的接收操作,發送操作和接收操作通常發生在不同的goroutine中(在同一個goroutine中執行兩個操作很容易導致死鎖)。無緩存的通道上的發送操作總在對應的接收操作完成前發?生

var done = make(chan bool)
var msg string
func aGoroutine() {
    msg = "你好, 世界"
    done <- true
}
func main() {
    go aGoroutine()
    <-done
    println(msg)
}

以上程序可保證打印出“你好, 世界”。該程序首先對msg進行寫入,然后在done通道上發送同步信號,隨后從done接收對應的同步信號,最后執行println()函?數。

若在關閉通道后繼續從中接收數據,接收者就會收到該通道返回的零值。因此在這個例子中,用close(done)關閉通道代替done <- true依然能保證該程序產生相同的行?為。

var done = make(chan bool)
var msg string
func aGoroutine() {
    msg = "你好, 世界"
    close(done)
}
func main() {
    go aGoroutine()
    <-done
    println(msg)
}

根據Go語言內存模型規范,對于從無緩存的通道進行的接收,發生在對該通道進行的發送完成之前。基于上面這個規則可知,交換兩個goroutine中的接收操作和發送操作也是可以的(但是很危險):

var done = make(chan bool)
var msg string
func aGoroutine() {
    msg = "hello, world"
    <-done
}
func main() {
    go aGoroutine()
    done <- true
    println(msg)
}

這樣也可保證打印出“hello, world”。因為main線程中done <- true發送完成前后臺線程<-done接收已經開始(這保證msg = "hello, world"被執行了),所以之后println(msg)msg已經被賦過值了。簡而言之,后臺線程首先對msg進行寫入,然后從done中接收信號,隨后main線程向done發送對應的信號,最后執行println()函數完成。但是,若該通道為帶緩存的(如done = make(chan bool, 1)),main線程的done <- true接收操作將不會被后臺線程的<-done接收操作阻塞,該程序將無法保證打印出“hello, world”。

對于帶緩存的通道,對通道中的第K個接收操作發生在第K+C個發送操作完成之前,其中C是通道的緩存大小。如果將C設置為0,自然就對應無緩存的通道,也就是第K個接收操作在第K個發送操作完成之前。因為無緩存的通道只能同步發1個,所以也就簡化為前面無緩存通道的規則:對于從無緩存的通道進行的接收,發生在對該通道進行的發送完成之?前

我們可以根據控制通道的緩存大小來控制并發執行的goroutine的最大數目,例如:

var limit = make(chan int, 3)
var work = []func(){
    func() { println("1"); time.Sleep(1 * time.Second) },
    func() { println("2"); time.Sleep(1 * time.Second) },
    func() { println("3"); time.Sleep(1 * time.Second) },
    func() { println("4"); time.Sleep(1 * time.Second) },
    func() { println("5"); time.Sleep(1 * time.Second) },
}
func main() {
    for _, w := range work {
        go func(w func()) {
            limit <- 1
            w()
            <-limit
        }(w)
    }
    select{}
}

在循環創建goroutine過程中,使用了匿名函數并在函數中引用了循環變量w,由于w是傳引用的而非傳值的,因此無法保證goroutine在運行時調用的w與循環創建時的w是同一個值。為了解決這個問題,可以利用函數傳遞參數的值副本來為每個goroutine單獨復制一份w

最后的select{}是一個空的通道選擇語句,該語句會導致main線程阻塞,從而避免程序過早退出。還有for{}<-make(chan int)等諸多方法可以達到類似的效果。因為main線程被阻塞了,如果需要程序正常退出,可以調用os.Exit(0)

主站蜘蛛池模板: 苗栗县| 垫江县| 新河县| 藁城市| 剑阁县| 威远县| 彭水| 南皮县| 尼木县| 阿拉尔市| 辽中县| 依安县| 南康市| 漳平市| 益阳市| 玉林市| 修水县| 宜兴市| 乌拉特后旗| 孟州市| 武安市| 手游| 共和县| 陆良县| 洛阳市| 南康市| 吴堡县| 泰州市| 朝阳区| 博爱县| 大安市| 酒泉市| 景德镇市| 喀喇沁旗| 花垣县| 大姚县| 晋中市| 张掖市| 乾安县| 京山县| 邢台县|