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

1.5.3 順序一致性內存模型

如果只是想簡單地在線程之間進行數據同步的話,原子操作已經為編程人員提供了一些同步保障。不過這種保障有一個前提:順序一致性內存模型。要了解順序一致性,先看一個簡單的例子:

var a string
var done bool
func setup() {
    a = "hello, world"
    done = true
}
func main() {
    go setup()
    for !done {}
    print(a)
}

我們創建了setup線程,用于對字符串a的初始化工作,初始化完成之后設置done標志為true。main()函數所在的主線程中,通過for !done {}檢測done變為true時,認為字符串初始化工作完成,然后進行字符串的打印工?作。

但是,Go語言并不保證在main()函數中觀測到的對done的寫入操作發生在對字符串a的寫入操作之后,因此程序很可能打印一個空字符串。更糟糕的是,因為兩個線程之間沒有同步事件,setup線程對done的寫入操作甚至無法被main線程看到,main()函數有可能陷入死循環?中。

在Go語言中,同一個goroutine內部,順序一致性內存模型是得到保證的。但是不同的goroutine之間,并不滿足順序一致性內存模型,需要通過明確定義的同步事件來作為同步的參考。如果兩個事件不可排序,那么就說這兩個事件是并發的。為了最大化并行,Go語言的編譯器和處理器在不影響上述規定的前提下可能會對執行語句重新排序(CPU也會對一些指令進行亂序執行)。

因此,如果在一個goroutine中順序執行a = 1;b = 2;兩個語句,雖然在當前的goroutine中可以認為a = 1;語句先于b = 2;語句執行,但是在另一個goroutine中b = 2;語句可能會先于a = 1;語句執行,甚至無法看到它們的變化(可能始終在寄存器中)。也就是說,在另一個goroutine看來,a = 1;b = 2;兩個語句的執行順序是不確定的。如果一個并發程序無法確定事件的順序關系,那么程序的運行結果往往會不確定。例如,下面這個程序:

func main() {
    go println("你好, 世界")
}

根據Go語言規范,main()函數退出時程序結束,不會等待任何后臺線程。但因為goroutine的執行和main()函數的返回事件是并發的,誰都有可能先發生,所以什么時候打印、能否打印都是未知?的。

用前面的原子操作并不能解決問題,因為我們無法確定兩個原子操作之間的順序。解決問題的辦法就是通過同步原語來給兩個事件明確排序:

func main() {
    done := make(chan int)
    go func(){
        println("你好, 世界")
        done <- 1
    }()
    <-done
}

<-done執行時,必然要求done <- 1也已經執行。根據同一個goroutine依然滿足順序一致性規則,可以判斷當done <- 1執行時,println("你好, 世界")語句必然已經執行完成了。因此,現在的程序確??梢哉4蛴〗Y?果。

當然,通過sync.Mutex互斥量也是可以實現同步的:

func main() {
    var mu sync.Mutex
    mu.Lock()
    go func(){
        println("你好, 世界")
        mu.Unlock()
    }()
    mu.Lock()
}

可以確定,后臺線程的mu.Unlock()必然在println("你好, 世界")完成后發生(同一個線程滿足順序一致性),main()函數的第二個mu.Lock()必然在后臺線程的mu.Unlock()之后發生(sync.Mutex保證),此時后臺線程的打印工作已經順利完成?了。

主站蜘蛛池模板: 屏南县| 洛隆县| 元朗区| 兰溪市| 兴文县| 金秀| 株洲县| 汾西县| 长武县| 左云县| 肇庆市| 乌拉特后旗| 京山县| 民和| 威海市| 宽城| 宁波市| 九龙坡区| 天柱县| 会昌县| 揭东县| 改则县| 灵武市| 白城市| 连江县| 铅山县| 莱阳市| 道真| 张家川| 白河县| 清水县| 镇雄县| 文登市| 霍林郭勒市| 甘洛县| 南部县| 凌源市| 彭州市| 石家庄市| 岚皋县| 二连浩特市|