同步(Synchronization)

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
  1. 初始化
    程式的初始化在一個獨立的goroutine中執行。在初始化過程中建立的goroutine將在 第一個用於初始化goroutine執行完成後啟動。
    如果包p匯入了包q,包q的init初始化函數將在包p的初始化之前執行。
    程式的入口函數 main.main 則是在所有的 init 函數執行完成 之後啟動。
    在任意init函數中新建立的goroutines,將在所有的init 函數完成後執行。
  2. goroutine的建立
    用於啟動goroutine的go語句在goroutine之前運行。
    例如,下面的程式:

    var a stringfunc f() { print(a)}func hello() { a = "hello, world" go f()}

    調用hello函數,會在某個時刻列印“hello, world”(有可能是在hello函數返回之後)。

  3. Channel communication 管道通訊
    用管道通訊是兩個goroutines之間同步的主要方法。在管道上執行的發送操作會關聯到該管道的 接收操作,這通常對應goroutines。
    管道上的發送操作發生在管道的接收完成之前(happens before)。
    例如這個程式:

    var c = make(chan int, 10)var a stringfunc f() { a = "hello, world" c <- 0}func main() { go f() <-c print(a)}

    可以確保會輸出"hello, world"。因為,a的賦值發生在向管道 c發送資料之前,而管道的發送操作在管道接收完成之前發生。 因此,在print 的時候,a已經被賦值。
    從一個unbuffered管道接收資料在向管道發送資料完成之前發送。下面的是樣本程式:

    package mainvar c = make(chan int)var a stringfunc f() { a = "hello, world" <-c}func main() { go f() c <- 0 print(a)}

    同樣可以確保輸出“hello, world”。因為,a的賦值在從管道接收資料 前發生,而從管道接收資料操作在向unbuffered 管道發送完成之前發生。所以,在print 的時候,a已經被賦值。
    如果用的是緩衝管道(如 c = make(chan int, 1) ),將不能保證輸出 “hello, world”結果(可能會是Null 字元串, 但肯定不會是他未知的字串, 或導致程式崩潰)。


  4. 包sync實現了兩種類型的鎖: sync.Mutex 和 sync.RWMutex。
    對於任意 sync.Mutex 或 sync.RWMutex 變數l。 如果 n < m ,那麼第n次 l.Unlock() 調用在第 m次 l.Lock() 調用返回前發生。
    例如程式:

    var l sync.Mutexvar a stringfunc f() { a = "hello, world" l.Unlock()}func main() { l.Lock() go f() l.Lock() print(a)}

    可以確保輸出“hello, world”結果。因為,第一次 l.Unlock() 調用(在f函數中)在第二次 l.Lock() 調用 (在main 函數中)返回之前發生,也就是在 print 函數調用之前發生。
    對於任何呼叫到一個sync.RWMutex變數l l.RLock,有一個n使得l.RLock第n個呼叫l.Unlock後發生(返回)和n +1'th之前的匹配l.RUnlock發生調用l.Lock。
    9.3.5. once
    包sync提供了一個在多個goroutines中進行初始化的方法。多個goroutines可以 通過 once.Do(f) 方式調用f函數。 但是,f函數 只會被執行一次,其他的調用將被阻塞直到唯一執行的f()返回。
    once.Do(f) 中唯一執行的f()發生在所有的 once.Do(f) 返回之前。
    有代碼:

    var once sync.Oncevar a stringfunc setup() { a = "hello, world"}func doprint() { once.Do(setup) print(a)}func twoprint() { go doprint() go doprint()}

    調用twoprint會輸出“hello, world”兩次。第一次twoprint 函數會運行setup唯一一次。

錯誤的同步方式

注意:變數讀操作雖然可以偵測到變數的寫操作,但是並不能保證對變數的讀操作就一定發生在寫操作之後。

例如:

package mainvar a, b intfunc f() {    a = 1    b = 2}func g() {    print(b)    print(a)}func main() {    go f()    g()}

函數g可能輸出2,也可能輸出0。

這種情形使得我們必須迴避一些看似合理的用法。

這裡用重複資料偵測的方法來代替同步。在例子中,twoprint函數可能得到錯誤的值:

package mainimport (    "sync"    "time")var once sync.Oncevar a stringvar done boolfunc setup() {    a = "hello, world"    done = true}func doprint() {    if !done {        once.Do(setup)    }    print(a)}func twoprint() {    go doprint()    go doprint()}func main() {    twoprint()    time.Sleep(8000)}

在doprint函數中,寫done暗示已經給a賦值了。 但是沒有辦法給出保證,函數可能輸出空的值(在2個goroutines中同時執行到測試語句)。

另一個錯誤陷阱是忙等待:

package mainvar a stringvar done boolfunc setup() {    a = "hello, world"    done = true}func main() {    go setup()    for !done {    }    print(a)}

我們沒有辦法保證在main中看到了done值被修改的同時也 能看到a被修改,因此程式可能輸出Null 字元串。 更壞的結果是,main 函數可能永遠不知道done被修改,因為在兩個線程之間沒有同步操作,這樣main 函數永遠不能返回。

下面的用法本質上也是同樣的問題.

package maintype T struct {    msg string}var g *Tfunc setup() {    t := new(T)    t.msg = "hello, world"    g = t}func main() {    go setup()    for g == nil {    }    print(g.msg)}

即使main觀察到了 g != nil 條件並且退出了迴圈,但是任何然 不能保證它看到了g.msg的初始化之後的結果。

在這些例子中,只有一種解決方案:用顯示的同步。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.