Go并發(fā)編程基礎(chǔ)_第1頁
Go并發(fā)編程基礎(chǔ)_第2頁
Go并發(fā)編程基礎(chǔ)_第3頁
Go并發(fā)編程基礎(chǔ)_第4頁
Go并發(fā)編程基礎(chǔ)_第5頁
已閱讀5頁,還剩4頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

1、go并發(fā)編程基礎(chǔ)本文是一篇并發(fā)編程方面的入門文章,以go語言編寫示例代碼, 內(nèi)容涵蓋:運(yùn)行期并發(fā)線程(coroutines)基本的同步技術(shù)(管道和鎖)go 語言中基本的并發(fā)模式死鎖和數(shù)據(jù)競(jìng)爭(zhēng)并行計(jì)算在開始閱讀本文之前,你應(yīng)該知道如何編寫簡(jiǎn)單的go程序。如 果你熟悉的是c/c+、java或python之類的語言,那么go語言之旅 能提供所有必要的背景知識(shí)。也許你還有興趣讀一讀為c+程序員準(zhǔn) 備的go語言教程或?yàn)閖ava程序員準(zhǔn)備的go語言教程。go允許使用go語句開啟一個(gè)新的運(yùn)行期線程,即goroutine,以 一個(gè)不同的、新創(chuàng)建的goroutine來執(zhí)行一個(gè)函數(shù)。同一個(gè)程序中的 所有g(shù)orou

2、tine共享同一個(gè)地址空間。goroutine非常輕量,除了為之分配的??臻g,其所占用的內(nèi)存 空間微乎其微。并且其??臻g在開始時(shí)非常小,之后隨著堆存儲(chǔ)空間 的按需分配或釋放而變化。內(nèi)部實(shí)現(xiàn)上,goroutine會(huì)在多個(gè)操作系 統(tǒng)線程上多路復(fù)用。如果一個(gè)goroutine阻塞了一個(gè)操作系統(tǒng)線程, 例如:等待輸入,這個(gè)線程上的其他goroutine就會(huì)遷移到其他線程, 這樣能繼續(xù)運(yùn)行。開發(fā)者并不需要關(guān)心/擔(dān)心這些細(xì)節(jié)。下面所示程序會(huì)輸出“ hello from main goroutine ”。也可能會(huì)輸 出 “hello from anothergoroutine,具體依賴于兩個(gè) gorouti

3、ne 哪個(gè)先 結(jié)束。接下來的這個(gè)程序,多數(shù)情況下,會(huì)輸出“ hello from main coroutine” 和 hello from anothergoroutine 輸出的順序不確定。 但還有另一個(gè)可能性是:第二個(gè)goroutine運(yùn)行得極其慢,在程序結(jié) 束之前都沒來得及輸出相應(yīng)的消息。下面則是一個(gè)相對(duì)更加實(shí)際的示例,其中定義了一個(gè)函數(shù)使用并 發(fā)來推遲觸發(fā)一個(gè)事件。你可能會(huì)這樣使用publish函數(shù):這個(gè)程序,絕大多數(shù)情況下,會(huì)輸出以下三行,順序固定,每行 輸出之間相隔5秒。一般來說,通過睡眠的方式來編排線程之間相互等待是不太可能 的。下一章節(jié)會(huì)介紹g。語言中的一種同步機(jī)制-管道,并演

4、示如 何使用管道讓一個(gè)goroutine等待另一個(gè)goroutine。2. 管道(channel)管道是go語言的一個(gè)構(gòu)件,提供一種機(jī)制用于兩個(gè)goroutine 之間通過傳遞一個(gè)指定類型的值來同步運(yùn)行和通訊。操作符<用于 指定管道的方向,發(fā)送或接收。如果未指定方向,則為雙向管道。管道是引用類型,基于make函數(shù)來分配。如果通過管道發(fā)送一個(gè)值,則將<作為二元操作符使用。通過 管道接收一個(gè)值,則將其作為一元操作符使用:如果管道不帶緩沖,發(fā)送方會(huì)阻塞直到接收方從管道中接收了值。 如果管道帶緩沖,發(fā)送方則會(huì)阻塞直到發(fā)送的值被拷貝到緩沖區(qū)內(nèi); 如果緩沖區(qū)已滿,則意味著需要等

5、待直到某個(gè)接收方獲取到一個(gè)值。 接收方在有值可以接收之前會(huì)一直阻塞。關(guān)閉管道(close)close函數(shù)標(biāo)志著不會(huì)再往某個(gè)管道發(fā)送值。在調(diào)用close之后, 并且在之前發(fā)送的值都被接收后,接收操作會(huì)返回一個(gè)零值,不會(huì)阻 塞。一個(gè)多返回值的接收操作會(huì)額外返回一個(gè)布爾值用來指示返回的 值是否發(fā)送操作傳遞的。一個(gè)帶有range 了句的for語句會(huì)依次讀取發(fā)往管道的值,直到 該管道關(guān)閉:3. 同步下一個(gè)示例中,我們讓publish函數(shù)返回一個(gè)管道-用于在發(fā) 布text變量值時(shí)廣播一條消息:注意:我們使用了一個(gè)空結(jié)構(gòu)體的管道:struct*這明確地指明 該管道僅用于發(fā)信號(hào),而不是傳遞數(shù)據(jù)。我們可能會(huì)這樣

6、使用這個(gè)函數(shù):這個(gè)程序會(huì)按指定的順序輸出以下三行內(nèi)容。最后一行在新聞(news) 一出就會(huì)立即輸出。4. 死鎖現(xiàn)在我們?cè)趐ublish函數(shù)中引入一個(gè)bug:主程序還是像之前一樣開始運(yùn)行:輸出第一行,然后等待5秒, 這時(shí)publish函數(shù)開啟的goroutine會(huì)輸出突發(fā)新聞(breaking news), 然后退出,留下主goroutine獨(dú)白等待。此刻之后,程序無法再繼續(xù)往下執(zhí)行。眾所周知,這種情形即為死鎖。go語言對(duì)于運(yùn)行時(shí)的死鎖檢測(cè)具備良好的支持。當(dāng)沒有任何 goroutine能夠往前執(zhí)行的情形發(fā)生時(shí),go程序通常會(huì)提供詳細(xì)的錯(cuò) 誤信息。以下就是我們的問題程序的輸出:大多數(shù)情況下找出go

7、程序中造成死鎖的原因都比較容易,那么剩下的就是如何解決這個(gè) bug 了 o5. 數(shù)據(jù)競(jìng)爭(zhēng)(data race)死鎖也許聽起來令人挺憂傷的,但伴隨并發(fā)編程真正災(zāi)難性的錯(cuò) 誤其實(shí)是數(shù)據(jù)競(jìng)爭(zhēng),相當(dāng)常見,也可能非常難于調(diào)試。下面的這個(gè)函數(shù)就有數(shù)據(jù)競(jìng)爭(zhēng)問題,其行為是未定義的。例如, 可能輸出數(shù)值1。代碼之后是一個(gè)可能性解釋,試圖搞清楚這一切是 如何發(fā)生得。代碼中的兩個(gè)goroutine(假設(shè)命名為gl和g2)參與了一次競(jìng)爭(zhēng),我們無法獲知操作會(huì)以何種順序發(fā)生。以下是諸多可能中的一種:凱從n中獲取值0g2從n中獲取值0型將值從0增大到lgl將1 寫到ng2將值從0增大到遼2將1寫到n程序輸出n的值,當(dāng)前為1

8、“數(shù)據(jù)競(jìng)爭(zhēng)(data race)”這名字有點(diǎn)誤導(dǎo)的嫌疑。不僅操作的 順序是未定義的,其實(shí)根本沒有任何保證(no guarantees whatsoever)q 編譯器和硬件為了得到更好的性能,經(jīng)常都會(huì)對(duì)代碼進(jìn)行上下內(nèi)外的 順序變換。如果你看到一個(gè)線程處于中間行為狀態(tài)時(shí),那么當(dāng)時(shí)的場(chǎng) 景可能就像下圖所示的一樣:避免數(shù)據(jù)競(jìng)爭(zhēng)的唯一方式是線程間同步訪問所有的共享可變數(shù) 據(jù)。有幾種方式能夠?qū)崿F(xiàn)這一目標(biāo)。go語言中,通常是使用管道或 者鎖。(sync和sync/atomic包中還有更低層次的機(jī)制可供使用,但木 文中不做討論)。go語言中,處理并發(fā)數(shù)據(jù)訪問的推薦方式是使用管道從一個(gè) goroutine中往

9、下一個(gè)goroutine傳遞實(shí)際的數(shù)據(jù)。有格言說得好:“不 要通過共享內(nèi)存來通訊,而是通過通訊來共享內(nèi)存”。以上代碼中的管道肩負(fù)雙重責(zé)任-從一個(gè)goroutine將數(shù)據(jù)傳 遞到另一個(gè)goroutine,并月起到同步的作用:發(fā)送方goroutine會(huì)等 待另一個(gè)goroutine接收數(shù)據(jù),接收方goroutine也會(huì)等待另一個(gè) goroutine發(fā)送數(shù)據(jù)。go語言內(nèi)存模型-要保證一個(gè)goroutine中對(duì)一個(gè)變量的讀操 作得到的值正好是另一個(gè)goroutine中對(duì)同一個(gè)變量寫操作產(chǎn)生的值,條件相當(dāng)復(fù)雜, 但goroutine之間只要通過管道來共享所有可變數(shù)據(jù),那么就能遠(yuǎn)離 數(shù)據(jù)競(jìng)爭(zhēng)了。6. 互斥

10、鎖有時(shí),通過顯式加鎖,而不是使用管道,來同步數(shù)據(jù)訪問,可能 更加便捷。go語言標(biāo)準(zhǔn)庫為這一目的提供了一個(gè)互斥鎖- syn c.mutexo要想這類加鎖起效的話,關(guān)鍵之處在于:所有對(duì)共享數(shù)據(jù)的訪問, 不管讀寫,僅當(dāng)goroutine持有鎖才能操作。一個(gè)goroutine出錯(cuò)就足 以破壞掉一個(gè)程序,引入數(shù)據(jù)競(jìng)爭(zhēng)。因此,應(yīng)該設(shè)計(jì)一個(gè)自定義數(shù)據(jù)結(jié)構(gòu),具備明確的api,確保所 有的同步都在數(shù)據(jù)結(jié)構(gòu)內(nèi)部完成。下例中,我們構(gòu)建了一個(gè)安全、易 于使用的并發(fā)數(shù)據(jù)結(jié)構(gòu),atomiclnt,用于存儲(chǔ)一個(gè)整型值。任意數(shù)量 的goroutine都能通過add和value方法安全地訪問這個(gè)數(shù)值。7. 檢測(cè)數(shù)據(jù)競(jìng)爭(zhēng)競(jìng)爭(zhēng)有時(shí)

11、非常難于檢測(cè)。下例中的這個(gè)函數(shù)有一個(gè)數(shù)據(jù)競(jìng)爭(zhēng)問題, 執(zhí)行這個(gè)程序時(shí)會(huì)輸出55555o嘗試一下,也許你會(huì)得到一個(gè)不同的 結(jié)果。(sync.waitgroup是go語言標(biāo)準(zhǔn)庫的一部分;用于等待一組 goroutine結(jié)束運(yùn)行。)對(duì)于輸出55555, 一個(gè)貌似合理的解釋是:執(zhí)行i+的goroutine 在其他goroutine執(zhí)行打印語句之前就完成了 5次i+操作。實(shí)際上變 量i更新后的值為其他goroutine所見純屬巧合。一個(gè)簡(jiǎn)單的解決方案是:使用一個(gè)局部變量,然后當(dāng)開啟新的 goroutine時(shí),將數(shù)值作為參數(shù)傳遞:這次代碼就對(duì)了,程序會(huì)輸出期望的結(jié)果,女2403lo注意:goroutine之

12、間的運(yùn)行順序是不確定的。仍舊使用閉包,但能夠避免數(shù)據(jù)競(jìng)爭(zhēng)也是可能的,必須小心地讓每個(gè)goroutine使用一個(gè)獨(dú)有的變量。數(shù)據(jù)競(jìng)爭(zhēng)自動(dòng)檢測(cè) 一般來說,不太可能能夠自動(dòng)檢測(cè)發(fā)現(xiàn)所有可能的數(shù)據(jù)競(jìng)爭(zhēng)情況,但go (從版本1.1開始)有一個(gè)強(qiáng)大的數(shù)據(jù)競(jìng)爭(zhēng)檢測(cè)器。這個(gè)工具用起來也很簡(jiǎn)單:只要在使用go命令時(shí)加上-race標(biāo)記 即可。開啟檢測(cè)器運(yùn)行上面的程序會(huì)給出清晰且信息量大的輸出:該工具發(fā)現(xiàn)一處數(shù)據(jù)競(jìng)爭(zhēng),包含:一個(gè)goroutine在第20行對(duì)一 個(gè)變量進(jìn)行寫操作,跟著另一個(gè)goroutine在第22行對(duì)同一個(gè)變量進(jìn) 行了未同步的讀操作。注意:競(jìng)爭(zhēng)檢測(cè)器只能發(fā)現(xiàn)在運(yùn)行期確實(shí)發(fā)生的數(shù)據(jù)競(jìng)爭(zhēng)(譯注: 我也

13、不太理解這話,請(qǐng)指導(dǎo))8. select 語句select語句是go語言并發(fā)工具集中的終極工具。select用于從一 組可能的通訊中選擇一個(gè)進(jìn)一步處理。如果任意一個(gè)通訊都可以進(jìn)一 步處理,則從中隨機(jī)選擇一個(gè),執(zhí)行對(duì)應(yīng)的語句。否則,如果又沒有 默認(rèn)分支(default case), select語句則會(huì)阻塞,直到其中一個(gè)通訊完 成。以下是一個(gè)玩具示例,演示select語句如何用于實(shí)現(xiàn)一個(gè)隨機(jī)數(shù) 生成器:下面是相對(duì)更加實(shí)際一點(diǎn)的例子:如何使用select語句為一個(gè)操 作設(shè)置一個(gè)時(shí)間限制。代碼會(huì)輸出變量news的值或者超時(shí)消息,具 體依賴于兩個(gè)接收語句哪個(gè)先執(zhí)行:函數(shù)time.after是go語言標(biāo)

14、準(zhǔn)庫的一部分;它會(huì)在等待指定時(shí) 間后將當(dāng)前的時(shí)間發(fā)送到返回的管道中。9.綜合所有示例 面的掌握。這個(gè)程序演示了如何將管道用于被任意數(shù)量的goroutine發(fā)送和 接收數(shù)據(jù),也演示了如何將select語句用于從多個(gè)通訊中選擇一個(gè)。示例輸出:10.并行計(jì)算并發(fā)的一個(gè)應(yīng)用是將一個(gè)大的計(jì)算切分成一些工作單元,調(diào)度到 不同的cpu上同時(shí)地計(jì)算。將計(jì)算分布到多個(gè)cpu上更多是一門藝 術(shù),而不是一門科學(xué)。以下是一些經(jīng)驗(yàn)法則:每個(gè)工作單元應(yīng)該花費(fèi)大約100微秒到1毫秒的時(shí)間用于計(jì)算。 如果單元粒度太小,切分問題以及調(diào)度子問題的管理開銷可能就會(huì)太 大。如果單元粒度太大,整個(gè)計(jì)算也許不得不等待一個(gè)慢的工作項(xiàng)結(jié) 束。這種緩慢可能因?yàn)槎喾N原因而產(chǎn)生,比如:調(diào)度、其他進(jìn)程的中 斷或者糟糕的內(nèi)存布局。(注意:工作單元的數(shù)目是不依賴于cpu的 數(shù)目的)盡可能減小共享的數(shù)據(jù)量。并發(fā)寫操作的代價(jià)非常大,特別 是如果goroutine運(yùn)行在不同的cpu上

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論