Go內(nèi)存模型指定了在何種條件下可以保證在一個(gè) goroutine 中讀取變量時(shí)觀察到不同 goroutine 中寫入該變量的值。

創(chuàng)新互聯(lián)長期為近千家客戶提供的網(wǎng)站建設(shè)服務(wù),團(tuán)隊(duì)從業(yè)經(jīng)驗(yàn)10年,關(guān)注不同地域、不同群體,并針對(duì)不同對(duì)象提供差異化的產(chǎn)品和服務(wù);打造開放共贏平臺(tái),與合作伙伴共同營造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為讓胡路企業(yè)提供專業(yè)的成都網(wǎng)站建設(shè)、成都網(wǎng)站制作,讓胡路網(wǎng)站改版等技術(shù)服務(wù)。擁有十年豐富建站經(jīng)驗(yàn)和眾多成功案例,為您定制開發(fā)。
通過多個(gè)協(xié)程并發(fā)修改數(shù)據(jù)的程序必須將操作序列化。為了序列化訪問,通過channel操作或者其他同步原語( sync 、 sync/atomic )來保護(hù)數(shù)據(jù)。
如果你必須要閱讀本文的其他部分才能理解你程序的行為,請(qǐng)盡量不要這樣...
在單個(gè) goroutine 中,讀取和寫入的行為必須像按照程序指定的順序執(zhí)行一樣。 也就是說,只有當(dāng)重新排序不會(huì)改變語言規(guī)范定義的 goroutine 中的行為時(shí),編譯器和處理器才可以重新排序在單個(gè) goroutine 中執(zhí)行的讀取和寫入。 由于這種重新排序,一個(gè) goroutine 觀察到的執(zhí)行順序可能與另一個(gè) goroutine 感知的順序不同。 例如,如果一個(gè) goroutine 執(zhí)行 a = 1; b = 2;,另一個(gè)可能會(huì)在 a 的更新值之前觀察到 b 的更新值。
為了滿足讀寫的需求,我們定義了 happens before ,Go程序中內(nèi)存操作的局部順序。如果事件 e1 在 e2 之前發(fā)生,我們說 e2 在 e1 之后發(fā)生。還有,如果 e1 不在 e2 之前發(fā)生、 e2 也不在 e1 之前發(fā)生,那么我們說 e1 和 e2 并發(fā)happen。
在單個(gè) goroutine 中, happens-before 順序由程序指定。
當(dāng)下面兩個(gè)條件滿足時(shí),變量 v 的閱讀操作 r 就 可能 觀察到寫入操作 w
為了保證 r 一定能閱讀到 v ,保證 w 是 r 能觀測(cè)到的唯一的寫操作。當(dāng)下面兩個(gè)條件滿足時(shí), r 保證可以讀取到 w
這一對(duì)條件比上一對(duì)條件更強(qiáng);這要求無論是 w 還是 r ,都沒有相應(yīng)的并發(fā)操作。
在單個(gè) goroutine 中,沒有并發(fā)。所以這兩個(gè)定義等價(jià):讀操作 r 能讀到最近一次 w 寫入 v 的值。但是當(dāng)多個(gè) goroutine 訪問共享變量時(shí),它們必須使用同步事件來建立 happens-before 關(guān)系。
使用變量 v 類型的0值初始化變量 v 的行為類似于內(nèi)存模型中的寫入。
對(duì)于大于單個(gè)機(jī)器字長的值的讀取和寫入表現(xiàn)為未指定順序的對(duì)多個(gè)機(jī)器字長的操作。
程序初始化在單個(gè) goroutine 中運(yùn)行,但該 goroutine 可能會(huì)創(chuàng)建其他并發(fā)運(yùn)行的 goroutine。
如果包 p 導(dǎo)入包 q,則 q 的 init 函數(shù)的完成發(fā)生在任何 p 的操作開始之前。
main.main 函數(shù)的啟動(dòng)發(fā)生在所有 init 函數(shù)完成之后。
go 語句啟動(dòng)新的協(xié)程發(fā)生在新協(xié)程啟動(dòng)開始之前。
舉個(gè)例子
調(diào)用 hello 將會(huì)打印 hello, world 。當(dāng)然,這個(gè)時(shí)候 hello 可能已經(jīng)返回了。
go 協(xié)程的退出并不保證發(fā)生在任何事件之前
對(duì) a 的賦值之后沒有任何同步事件,因此不能保證任何其他 goroutine 都會(huì)觀察到它。 事實(shí)上,激進(jìn)的編譯器可能會(huì)刪除整個(gè) go 語句。
如果一個(gè) goroutine 的效果必須被另一個(gè) goroutine 觀察到,請(qǐng)使用同步機(jī)制,例如鎖或通道通信來建立相對(duì)順序。
通道通信是在go協(xié)程之間傳輸數(shù)據(jù)的主要手段。在特定通道上的發(fā)送總有一個(gè)對(duì)應(yīng)的channel的接收,通常是在另外一個(gè)協(xié)程。
channel 上的發(fā)送發(fā)生在對(duì)應(yīng) channel 接收之前
程序能保證輸出 hello, world 。對(duì)a的寫入發(fā)生在往 c 發(fā)送數(shù)據(jù)之前,往 c 發(fā)送數(shù)據(jù)又發(fā)生在從 c 接收數(shù)據(jù)之前,它又發(fā)生在 print 之前。
channel 的關(guān)閉發(fā)生在從 channel 中獲取到0值之前
在之前的例子中,將 c-0 替換為 close(c) ,程序還是能保證輸出 hello, world
無buffer channel 的接收發(fā)生在發(fā)送操作完成之前
這個(gè)程序,和之前一樣,但是調(diào)換發(fā)送和接收操作,并且使用無buffer的channel
也保證能夠輸出 hello, world 。對(duì)a的寫入發(fā)生在c的接收之前,繼而發(fā)生在c的寫入操作完成之前,繼而發(fā)生在print之前。
如果該 channel 是buffer channel (例如: c=make(chan int, 1) ),那么程序就不能保證輸出 hello, world 。可能會(huì)打印空字符串、崩潰等等。從而,我們得到一個(gè)相對(duì)通用的推論:
對(duì)于容量為C的buffer channel來說,第k次從channel中接收,發(fā)生在第 k + C 次發(fā)送完成之前。
此規(guī)則將先前的規(guī)則推廣到緩沖通道。 它允許通過buffer channel 來模擬信號(hào)量:通道中的條數(shù)對(duì)應(yīng)活躍的數(shù)量,通道的容量對(duì)應(yīng)于最大并發(fā)數(shù)。向channel發(fā)送數(shù)據(jù)相當(dāng)于獲取信號(hào)量,從channel中接收數(shù)據(jù)相當(dāng)于釋放信號(hào)量。 這是限制并發(fā)的常用習(xí)慣用法。
該程序?yàn)楣ぷ髁斜碇械拿總€(gè)條目啟動(dòng)一個(gè) goroutine,但是 goroutine 使用 limit channel進(jìn)行協(xié)調(diào),以確保一次最多三個(gè)work函數(shù)正在運(yùn)行。
sync 包中實(shí)現(xiàn)了兩種鎖類型: sync.Mutex 和 sync.RWMutex
對(duì)于任何的 sync.Mutex 或者 sync.RWMutex 變量 ,且有 nm ,第 n 個(gè)調(diào)用 UnLock 一定發(fā)生在 m 個(gè) Lock`之前。
這個(gè)程序也保證輸出 hello,world 。第一次調(diào)用 unLock 一定發(fā)生在第二次 Lock 調(diào)用之前
對(duì)于任何 sync.RWMutex 的 RLock 方法調(diào)用,存在變量n,滿足 RLock 方法發(fā)生在第 n 個(gè) UnLock 調(diào)用之后,并且對(duì)應(yīng)的 RUnlock 發(fā)生在第 n+1 個(gè) Lock 方法之前。
在存在多個(gè) goroutine 時(shí), sync 包通過 once 提供了一種安全的初始化機(jī)制。對(duì)于特定的 f ,多個(gè)線程可以執(zhí)行 once.Do(f) ,但是只有一個(gè)會(huì)運(yùn)行 f() ,另一個(gè)調(diào)用會(huì)阻塞,直到 f() 返回
從 once.Do(f) 對(duì) f() 的單個(gè)調(diào)用返回在任何一個(gè) once.Do(f) 返回之前。
調(diào)用 twoprint 將只調(diào)用一次 setup。 setup 函數(shù)將在任一打印調(diào)用之前完成。 結(jié)果將是 hello, world 打印兩次。
注意,讀取 r 有可能觀察到了由寫入 w 并發(fā)寫入的值。盡管觀察到了這個(gè)值,也并不意味著 r 后續(xù)的讀取可以讀取到 w 之前的寫入。
有可能 g 會(huì)接連打印2和0兩個(gè)值。
雙檢查鎖是為了降低同步造成的開銷。舉個(gè)例子, twoprint 方法可能會(huì)被誤寫成
因?yàn)闆]有任何機(jī)制保證,協(xié)程觀察到done為true的同時(shí)可以觀測(cè)到a為 hello, world ,其中有一個(gè) doprint 可能會(huì)輸出空字符。
另外一個(gè)例子
和以前一樣,不能保證在 main 中,觀察對(duì) done 的寫入意味著觀察對(duì) a 的寫入,因此該程序也可以打印一個(gè)空字符串。 更糟糕的情況下,由于兩個(gè)線程之間沒有同步事件,因此無法保證 main 會(huì)觀察到對(duì) done 的寫入。 main 中的循環(huán)會(huì)一直死循環(huán)。
下面是該例子的一個(gè)更微妙的變體
盡管 main 觀測(cè)到g不為nil,但是也沒有任何機(jī)制保證可以讀取到t.msg。
在上述例子中,解決方案都是相同的:請(qǐng)使用顯式的同步機(jī)制。
基本設(shè)計(jì)思路:
類型轉(zhuǎn)換、類型斷言、動(dòng)態(tài)派發(fā)。iface,eface。
反射對(duì)象具有的方法:
編譯優(yōu)化:
內(nèi)部實(shí)現(xiàn):
實(shí)現(xiàn) Context 接口有以下幾個(gè)類型(空實(shí)現(xiàn)就忽略了):
互斥鎖的控制邏輯:
設(shè)計(jì)思路:
(以上為寫被讀阻塞,下面是讀被寫阻塞)
總結(jié),讀寫鎖的設(shè)計(jì)還是非常巧妙的:
設(shè)計(jì)思路:
WaitGroup 有三個(gè)暴露的函數(shù):
部件:
設(shè)計(jì)思路:
結(jié)構(gòu):
Once 只暴露了一個(gè)方法:
實(shí)現(xiàn):
三個(gè)關(guān)鍵點(diǎn):
細(xì)節(jié):
讓多協(xié)程任務(wù)的開始執(zhí)行時(shí)間可控(按順序或歸一)。(Context 是控制結(jié)束時(shí)間)
設(shè)計(jì)思路: 通過一個(gè)鎖和內(nèi)置的 notifyList 隊(duì)列實(shí)現(xiàn),Wait() 會(huì)生成票據(jù),并將等待協(xié)程信息加入鏈表中,等待控制協(xié)程中發(fā)送信號(hào)通知一個(gè)(Signal())或所有(Boardcast())等待者(內(nèi)部實(shí)現(xiàn)是通過票據(jù)通知的)來控制協(xié)程解除阻塞。
暴露四個(gè)函數(shù):
實(shí)現(xiàn)細(xì)節(jié):
部件:
包: golang.org/x/sync/errgroup
作用:開啟 func() error 函數(shù)簽名的協(xié)程,在同 Group 下協(xié)程并發(fā)執(zhí)行過程并收集首次 err 錯(cuò)誤。通過 Context 的傳入,還可以控制在首次 err 出現(xiàn)時(shí)就終止組內(nèi)各協(xié)程。
設(shè)計(jì)思路:
結(jié)構(gòu):
暴露的方法:
實(shí)現(xiàn)細(xì)節(jié):
注意問題:
包: "golang.org/x/sync/semaphore"
作用:排隊(duì)借資源(如錢,有借有還)的一種場(chǎng)景。此包相當(dāng)于對(duì)底層信號(hào)量的一種暴露。
設(shè)計(jì)思路:有一定數(shù)量的資源 Weight,每一個(gè) waiter 攜帶一個(gè) channel 和要借的數(shù)量 n。通過隊(duì)列排隊(duì)執(zhí)行借貸。
結(jié)構(gòu):
暴露方法:
細(xì)節(jié):
部件:
細(xì)節(jié):
包: "golang.org/x/sync/singleflight"
作用:防擊穿。瞬時(shí)的相同請(qǐng)求只調(diào)用一次,response 被所有相同請(qǐng)求共享。
設(shè)計(jì)思路:按請(qǐng)求的 key 分組(一個(gè) *call 是一個(gè)組,用 map 映射存儲(chǔ)組),每個(gè)組只進(jìn)行一次訪問,組內(nèi)每個(gè)協(xié)程會(huì)獲得對(duì)應(yīng)結(jié)果的一個(gè)拷貝。
結(jié)構(gòu):
邏輯:
細(xì)節(jié):
部件:
如有錯(cuò)誤,請(qǐng)批評(píng)指正。
1、學(xué)習(xí)曲線
它包含了類C語法、GC內(nèi)置和工程工具。這一點(diǎn)非常重要,因?yàn)镚o語言容易學(xué)習(xí),所以一個(gè)普通的大學(xué)生花一個(gè)星期就能寫出來可以上手的、高性能的應(yīng)用。在國內(nèi)大家都追求快,這也是為什么國內(nèi)Go流行的原因之一。
2、效率
Go擁有接近C的運(yùn)行效率和接近PHP的開發(fā)效率,這就很有利的支撐了上面大家追求快速的需求。
3、出身名門、血統(tǒng)純正
之所以說Go語言出身名門,是因?yàn)槲覀冎繥o語言出自Google公司,這個(gè)公司在業(yè)界的知名度和實(shí)力自然不用多說。Google公司聚集了一批牛人,在各種編程語言稱雄爭(zhēng)霸的局面下推出新的編程語言,自然有它的戰(zhàn)略考慮。而且從Go語言的發(fā)展態(tài)勢(shì)來看,Google對(duì)它這個(gè)新的寵兒還是很看重的,Go自然有一個(gè)良好的發(fā)展前途。我們看看Go語言的主要?jiǎng)?chuàng)造者,血統(tǒng)純正這點(diǎn)就可見端倪了。
4、組合的思想、無侵入式的接口
Go語言可以說是開發(fā)效率和運(yùn)行效率二者的完美融合,天生的并發(fā)編程支持。Go語言支持當(dāng)前所有的編程范式,包括過程式編程、面向?qū)ο缶幊桃约昂瘮?shù)式編程。
5、強(qiáng)大的標(biāo)準(zhǔn)庫
這包括互聯(lián)網(wǎng)應(yīng)用、系統(tǒng)編程和網(wǎng)絡(luò)編程。Go里面的標(biāo)準(zhǔn)庫基本上已經(jīng)是非常穩(wěn)定,特別是我這里提到的三個(gè),網(wǎng)絡(luò)層、系統(tǒng)層的庫非常實(shí)用。
6、部署方便
我相信這一點(diǎn)是很多人選擇Go的最大理由,因?yàn)椴渴鹛奖悖袁F(xiàn)在也有很多人用Go開發(fā)運(yùn)維程序。
7、簡(jiǎn)單的并發(fā)
它包含降低心智的并發(fā)和簡(jiǎn)易的數(shù)據(jù)同步,我覺得這是Go最大的特色。之所以寫正確的并發(fā)、容錯(cuò)和可擴(kuò)展的程序如此之難,是因?yàn)槲覀冇昧隋e(cuò)誤的工具和錯(cuò)誤的抽象,Go可以說這一塊做的相當(dāng)簡(jiǎn)單。
8、穩(wěn)定性
Go擁有強(qiáng)大的編譯檢查、嚴(yán)格的編碼規(guī)范和完整的軟件生命周期工具,具有很強(qiáng)的穩(wěn)定性,穩(wěn)定壓倒一切。那么為什么Go相比于其他程序會(huì)更穩(wěn)定呢?這是因?yàn)镚o提供了軟件生命周期的各個(gè)環(huán)節(jié)的工具,如go
tool、gofmt、go test。
部署簡(jiǎn)單。Go編譯生成的是一個(gè)靜態(tài)可執(zhí)行文件,除了glibc外沒有其他外部依賴。這讓部署變得異常方便:目標(biāo)機(jī)器上只需要一個(gè)基礎(chǔ)的系統(tǒng)和必要的管理、監(jiān)控工具,完全不需要操心應(yīng)用所需的各種包、庫的依賴關(guān)系,大大減輕了維護(hù)的負(fù)擔(dān)。這和Python有著巨大的區(qū)別。由于歷史的原因,Python的部署工具生態(tài)相當(dāng)混亂【比如setuptools,distutils,pip,
buildout的不同適用場(chǎng)合以及兼容性問題】。官方PyPI源又經(jīng)常出問題,需要搭建私有鏡像,而維護(hù)這個(gè)鏡像又要花費(fèi)不少時(shí)間和精力。
并發(fā)性好。Goroutine和channel使得編寫高并發(fā)的服務(wù)端軟件變得相當(dāng)容易,很多情況下完全不需要考慮鎖機(jī)制以及由此帶來的各種問題。單個(gè)Go應(yīng)用也能有效的利用多個(gè)CPU核,并行執(zhí)行的性能好。這和Python也是天壤之比。多線程和多進(jìn)程的服務(wù)端程序編寫起來并不簡(jiǎn)單,而且由于全局鎖GIL的原因,多線程的Python程序并不能有效利用多核,只能用多進(jìn)程的方式部署;如果用標(biāo)準(zhǔn)庫里的multiprocessing包又會(huì)對(duì)監(jiān)控和管理造成不少的挑戰(zhàn)【我們用的supervisor管理進(jìn)程,對(duì)fork支持不好】。部署Python應(yīng)用的時(shí)候通常是每個(gè)CPU核部署一個(gè)應(yīng)用,這會(huì)造成不少資源的浪費(fèi),比如假設(shè)某個(gè)Python應(yīng)用啟動(dòng)后需要占用100MB內(nèi)存,而服務(wù)器有32個(gè)CPU核,那么留一個(gè)核給系統(tǒng)、運(yùn)行31個(gè)應(yīng)用副本就要浪費(fèi)3GB的內(nèi)存資源。
良好的語言設(shè)計(jì)。從學(xué)術(shù)的角度講Go語言其實(shí)非常平庸,不支持許多高級(jí)的語言特性;但從工程的角度講,Go的設(shè)計(jì)是非常優(yōu)秀的:規(guī)范足夠簡(jiǎn)單靈活,有其他語言基礎(chǔ)的程序員都能迅速上手。更重要的是Go自帶完善的工具鏈,大大提高了團(tuán)隊(duì)協(xié)作的一致性。比如gofmt自動(dòng)排版Go代碼,很大程度上杜絕了不同人寫的代碼排版風(fēng)格不一致的問題。把編輯器配置成在編輯存檔的時(shí)候自動(dòng)運(yùn)行g(shù)ofmt,這樣在編寫代碼的時(shí)候可以隨意擺放位置,存檔的時(shí)候自動(dòng)變成正確排版的代碼。此外還有g(shù)ofix,
govet等非常有用的工具。
執(zhí)行性能好。雖然不如C和Java,但通常比原生Python應(yīng)用還是高一個(gè)數(shù)量級(jí)的,適合編寫一些瓶頸業(yè)務(wù)。內(nèi)存占用也非常省。
網(wǎng)站欄目:go語言信號(hào)量,Go語言特性
網(wǎng)站路徑:http://chinadenli.net/article39/dseessh.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供定制開發(fā)、品牌網(wǎng)站設(shè)計(jì)、標(biāo)簽優(yōu)化、網(wǎng)站設(shè)計(jì)公司、企業(yè)建站、網(wǎng)站設(shè)計(jì)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)