亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Golang的內存模型介紹

發布時間:2020-05-22 17:39:47 來源:億速云 閱讀:185 作者:鴿子 欄目:編程語言

Go的內存模型詳述了"在一個groutine中對變量進行讀操作能夠偵測到在其他goroutine中對該變量的寫操作"的條件.

Happens Before

對于一個goroutine來說,它其中變量的讀, 寫操作執行表現必須和從所寫的代碼得出的預期是一致的。也就是說,在不改變程序表現的情況下,編譯器和處理器為了優化代碼可能會改變變量的操作順序即: 指令亂序重排。

但是在兩個不同的goroutine對相同變量操作時, 會因為指令重排導致不同的goroutine對變量的操作順序的認識變得不一致。例如,一個goroutine執行a = 1; b = 2;,在另一個goroutine中可能會現感知到變量b先于變量a被改變。

為了解決這種二義性問題,Go語言中引進一個happens before的概念,它用于描述對內存操作的先后順序問題。如果事件e1 happens before 事件 e2,我們說事件e2 happens after e1。

如果,事件e1 does not happen before 事件 e2,并且 does not happen after e2,我們說事件e1和e2同時發生。

對于一個單一的goroutine,happens before 的順序和代碼的順序是一致的。

如果能滿足以下的條件,一個對變量v的 “讀事件r” 可以感知到另一個對變量v的 “寫事件w” :

1、“寫事件w” happens before “讀事件r” 。

2、沒有既滿足 happens after w 同時滿主 happens before r 的對變量v的寫事件w。

為了保證讀事件r可以感知對變量v的寫事件,我們首先要確保w是變量v的唯一的寫事件。同時還要滿足以下條件:

1、“寫事件w” happens before “讀事件r”。

2、其他對變量v的訪問必須 happens before “寫事件w” 或者 happens after “讀事件r”。

第二組條件比第一組條件更加嚴格。因為,它要求在w和 r并行執行的程序中不能再有其他的讀操作。

對于在單一的goroutine中兩組條件是等價的,讀事件可以確保感知到對變量的寫事件。但是,對于在 兩個goroutines共享變量v,我們必須通過同步事件來保證 happens-before 條件 (這是讀事件感知寫事件的必要條件)。

將變量v自動初始化為零也是屬于這個內存操作模型。

讀寫超過一個機器字長度的數據,順序也是不能保證的。

同步(Synchronization)

初始化

程序的初始化在一個獨立的goroutine中執行。在初始化過程中創建的goroutine將在 第一個用于初始化goroutine執行完成后啟動。

如果包p導入了包q,包q的init 初始化函數將在包p的初始化之前執行。

程序的入口函數 main.main 則是在所有的 init 函數執行完成之后啟動。

在任意init函數中新創建的goroutines,將在所有的init 函數完成后執行。

Goroutine的創建

用于啟動goroutine的go語句在goroutine之前運行。

例如,下面的程序:

var a string;

func f() {
        print(a);
}

func hello() {
        a = "hello, world";
        go f();
}

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

Channel communication 管道通信

用管道通信是兩個goroutines之間同步的主要方法。通常的用法是不同的goroutines對同一個管道進行讀寫操作,一個goroutines寫入到管道中,另一個goroutines從管道中讀數據。

管道上的發送操作發生在管道的接收完成之前(happens before)。

例如這個程序:

var c = make(chan int, 10)
var a string

func f() {
        a = "hello, world";
        c <- 0;
}

func main() {
        go f();
        <-c;
        print(a);
}

可以確保會輸出"hello, world"。因為,a的賦值發生在向管道 c發送數據之前,而管道的發送操作在管道接收完成之前發生。因此,在print 的時候,a已經被賦值。

從一個unbuffered管道接收數據在向管道發送數據完成之前發送。

下面的是示例程序:

var c = make(chan int)
var a string

func 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”結果(可能會是空字符串,但肯定不會是他未知的字符串, 或導致程序崩潰)。

包sync實現了兩種類型的鎖: sync.Mutex 和 sync.RWMutex。

對于任意 sync.Mutex 或 sync.RWMutex 變量l。 如果 n < m ,那么第n次 l.Unlock() 調用在第 m次 l.Lock()調用返回前發生。

例如程序:

var l sync.Mutex
var a string

func 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 函數調用之前發生。

For any call to l.RLock on a sync.RWMutex variable l, there is an n such that the l.RLock happens (returns) after the n'th call to l.Unlock and the matching l.RUnlock happens before the n+1'th call to l.Lock.

Once

包once提供了一個在多個goroutines中進行初始化的方法。多個goroutines可以 通過 once.Do(f) 方式調用f函數。但是,f函數 只會被執行一次,其他的調用將被阻塞直到唯一執行的f()返回。once.Do(f) 中唯一執行的f()發生在所有的 once.Do(f) 返回之前。

有代碼:

var a string

func setup() {
        a = "hello, world";
}

func doprint() {
        once.Do(setup);
        print(a);
}

func twoprint() {
        go doprint();
        go doprint();
}

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

錯誤的同步方式

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

例如:

var a, b int

func f() {
        a = 1;
        b = 2;
}

func g() {
        print(b);
        print(a);
}

func main() {
        go f();
        g();
}

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

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

這里用Double-checked locking的方法來代替同步。在例子中,twoprint函數可能得到錯誤的值:

var a string
var done bool

func setup() {
        a = "hello, world";
        done = true;
}

func doprint() {
        if !done {
                once.Do(setup);
        }
        print(a);
}

func twoprint() {
        go doprint();
        go doprint();
}

在doprint函數中,寫done暗示已經給a賦值了,但是沒有辦法給出保證這一點,所以函數可能輸出空的值。

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

var a string
var done bool

func setup() {
        a = "hello, world";
        done = true;
}

func main() {
        go setup();
        for !done {
        }
        print(a);
}

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

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

type T struct {
        msg string;
}

var g *T

func 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的初始化之后的結果。

以上就是Go語言的內存模型介紹的詳細內容,更多請關注億速云其它相關文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

葵青区| 宁德市| 屯昌县| 淮北市| 五河县| 任丘市| 河津市| 旬邑县| 什邡市| 昔阳县| 普定县| 沭阳县| 双流县| 定襄县| 绥宁县| 偏关县| 海门市| 遂宁市| 特克斯县| 泰安市| 镇康县| 巴林左旗| 长沙县| 攀枝花市| 岐山县| 任丘市| 交口县| 广德县| 烟台市| 开鲁县| 花莲市| 富顺县| 泰宁县| 阿克陶县| 彭州市| 巫溪县| 政和县| 四子王旗| 峨眉山市| 鄢陵县| 榆中县|