《Go語言學(xué)習(xí)筆記》(雨痕)電子書網(wǎng)盤下載免費在線閱讀
創(chuàng)新互聯(lián)建站主要從事網(wǎng)站建設(shè)、網(wǎng)站設(shè)計、網(wǎng)頁設(shè)計、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)劍川,十多年網(wǎng)站建設(shè)經(jīng)驗,價格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):18982081108
鏈接:
提取碼:qyzq ?
書名:Go語言學(xué)習(xí)筆記
豆瓣評分:8.1
作者:?雨痕
出版社:?電子工業(yè)出版社
出品方:?博文視點
出版年:?2016-6
頁數(shù):?468
內(nèi)容簡介
作為時下流行的一種系統(tǒng)編程語言,Go 簡單易學(xué),性能很好,且支持各類主流平臺。已有大量項目采用 Go 編寫,這其中就包括 Docker 等明星作品,其開發(fā)和執(zhí)行效率早已被證明。本書經(jīng)四年多逐步完善,內(nèi)容覆蓋了語言、運行時、性能優(yōu)化、工具鏈等各層面知識。且內(nèi)容經(jīng)大量讀者反饋和校對,沒有明顯的缺陷和錯誤。上卷細(xì)致解析了語言規(guī)范相關(guān)細(xì)節(jié),便于讀者深入理解語言相關(guān)功能的使用方法和注意事項。下卷則對運行時源碼做出深度剖析,引導(dǎo)讀者透徹了解語言功能背后的支持環(huán)境和運行體系,諸如內(nèi)存分配、垃圾回收和并發(fā)調(diào)度等。本書不適合編程初學(xué)入門,可供有實際編程經(jīng)驗或正在使用Go 工作的人群參考。
作者簡介??
自 1996 年從事計算機(jī)軟件開發(fā)工作以來,已 20 春秋。期間供職于北大方正、西單電子商務(wù)、九城數(shù)碼、知乎等公司。主要從事核心開發(fā)、架構(gòu)設(shè)計,以及部分管理工作。
———文章來源 YamiOdymel/PHP-to-Golang
PHP和模塊之間的關(guān)系令人感到煩躁,假設(shè)你要讀取 yaml 檔案,你需要有一個 yaml 的模塊,為此,你還需要將其編譯然后將編譯后的模塊擺放至指定位置,之后換了一臺伺服器你還要重新編譯,這點到現(xiàn)在還是沒有改善;順帶一提之后出了PHP 7效能確實提升了許多(比Python 3快了些),但PHP仍令我感到臃腫,我覺得是時候
(轉(zhuǎn)行)了。
PHP 和Golang 的效能我想毋庸置疑是后者比較快(而且是以倍數(shù)來算),也許有的人會認(rèn)為兩種不應(yīng)該被放在一起比較,但Golang 本身就是偏向Web 開發(fā)的,所以這也是為什么我考慮轉(zhuǎn)用Golang 的原因,起初我的考慮有幾個:Node.js 和Rust 還有最終被選定的Golang;先談?wù)凬ode.js 吧。
Node.js的效能可以說是快上PHP 3.5倍至6倍左右 ,而且撰寫的語言還是JavaScript,蒸蚌,如此一來就不需要學(xué)習(xí)新語言了!搭配Babel更可以說是萬能,不過那跟「跳跳虎」一樣的Async邏輯還有那恐怖的Callback Hell,有人認(rèn)為前者是種優(yōu)點,這點我不否認(rèn),但是對學(xué)習(xí)PHP的我來說太過于"Mind Fuck",至于后者的Callback Hell雖然有Promise,但是那又是另一個「Then Hell」的故事了。相較于Golang之下,Node.js似乎就沒有那么吸引我了。你確實可以用Node.js寫出很多東西,不過那V8引擎的效能仍然有限,而且要學(xué)習(xí)新的事物,不就應(yīng)該是「全新」的嗎;)?
題外話: 為什么Node.js不適合大型和商業(yè)專案?
在拋棄改用Node.js 之后我曾經(jīng)花了一天的時間嘗試Rust 和Iron 框架,嗯??Rust 太強(qiáng)大了,強(qiáng)大到讓我覺得Rust 不應(yīng)該用在這里,這想法也許很蠢,但Rust 讓我覺得適合更應(yīng)該拿來用在系統(tǒng)或者是部分底層的地方,而不應(yīng)該是網(wǎng)路服務(wù)。
Golang是我最終的選擇,主要在于我花了一天的時間來研究的時候意外地發(fā)現(xiàn)Golang夭壽簡潔( 關(guān)鍵字只有25個 ),相較之下Rust太過于「強(qiáng)大」令我怯步;而且Golang帶有許多工具,例如 go fmt 會自動幫你整理程式碼、 go doc 會自動幫你生產(chǎn)文件、 go test 可以自動單元測試并生產(chǎn)覆蓋率報表、也有 go get 套件管理工具(雖然沒有版本功能),不過都很實用,而且也不需要加上分號( ; ),真要說不好的地方??大概就是強(qiáng)迫你花括號不能換行放吧(沒錯,我就是花括號會換行放的人)。
當(dāng)我在撰寫這份文件的時候 我會先假設(shè)你有一定的基礎(chǔ) ,你可以先閱讀下列的手冊,他們都很不錯。
你能夠在PHP 里面想建立一個變數(shù)的時候就直接建立,夭壽贊,是嗎?
蒸蚌!那么Golang 呢?在Golang 中變數(shù)分為幾類:「新定義」、「預(yù)先定義」、「自動新定義」、「覆蓋」。讓我們來看看范例:
在PHP中你會很常用到 echo 來顯示文字,像這樣。
然而在Golang中你會需要 fmt 套件,關(guān)于「什么是套件」的說明你可以在文章下述了解。
這很簡單,而且兩個語言的用法相差甚少,下面這是PHP:
只是Golang 稍微聒噪了一點,你必須在函式后面宣告他最后會回傳什么資料型別。
在PHP 中你要回傳多個資料你就會用上陣列,然后將資料放入陣列里面,像這樣。
然而在Golang 中你可以不必用到一個陣列,函式可以一次回傳多個值:
兩個語言的撰寫方式不盡相同。
主要是PHP 的陣列能做太多事情了,所以在PHP 里面要儲存什么用陣列就好了。
在Golang里??沒有這么萬能的東西,首先要先了解Golang中有這些型態(tài): array , slice , map , interface ,
你他媽的我到底看了三洨,首先你要知道Golang是個強(qiáng)型別語言,意思是你的陣列中 只能有一種型態(tài) ,什么意思?當(dāng)你決定這個陣列是用來擺放字串資料的時候,你就只能在里面放字串。沒有數(shù)值、沒有布林值,就像你沒有女朋友一樣。
先撇開PHP 的「萬能陣列」不管,Golang 中的陣列既單純卻又十分腦殘,在定義一個陣列的時候,你必須給他一個長度還有其內(nèi)容存放的資料型態(tài),你的陣列內(nèi)容不一定要填滿其長度,但是你的陣列內(nèi)容不能超過你當(dāng)初定義的長度。
切片??這聽起來也許很奇怪,但是你確實可以「切」他,讓我們先談?wù)劇盖衅贡绕稹戈嚵小挂迷谀睦铮骸改悴挥枚x其最大長度,而且你可以直接賦予值」,沒了。
我們剛才有提到你可以「切」他,記得嗎?這有點像是PHP中的 array_slice() ,但是Golang直接讓Slice「內(nèi)建」了這個用法,其用法是: slice[開始:結(jié)束] 。
在PHP中倒是沒有那么方便,在下列PHP范例中你需要不斷地使用 array_slice() 。
你可以把「映照」看成是一個有鍵名和鍵值的陣列,但是記?。骸改阈枰孪榷x其鍵名、鍵值的資料型態(tài)」,這仍限制你沒辦法在映照中存放多種不同型態(tài)的資料。
在Golang里可就沒這么簡單了,你需要先用 make() 宣告 map 。
也許你不喜歡「接口」這個詞,但用「介面」我怕會誤導(dǎo)大眾,所以,是的,接下來我會繼續(xù)稱其為「接口」。還記得你可以在PHP 的關(guān)聯(lián)陣列里面存放任何型態(tài)的資料嗎,像下面這樣?
現(xiàn)在你有福了!正因為Golang中的 interface{} 可以接受任何內(nèi)容,所以你可以把它拿來存放任何型態(tài)的資料。
有時候你也許會有個不定值的變數(shù),在PHP 里你可以直接將一個變數(shù)定義成字串、數(shù)值、空值、就像你那變心的女友一樣隨時都在變。
在Golang中你必須給予變數(shù)一個指定的資料型別,不過還記得剛才提到的:「Golang中有個 interface{} 能夠 存放任何事物 」嗎( 雖然也不是真的任何事物啦?? )?
當(dāng)我們程式中不需要繼續(xù)使用到某個資源或是發(fā)生錯誤的時候,我們索性會將其關(guān)閉或是拋棄來節(jié)省資源開銷,例如PHP 里的讀取檔案:
在Golang中,你可以使用 defer 來在函式結(jié)束的時候自動執(zhí)行某些程式(其執(zhí)行方向為反向)。所以你就不需要在函式最后面結(jié)束最前面的資源。
defer 可以被稱為「推遲執(zhí)行」,實際上就是在函式結(jié)束后會「反序」執(zhí)行的東西,例如你按照了這樣的順序定義 defer : A-B-C-D ,那么執(zhí)行的順序其實會是 D-C-B-A ,這用在程式結(jié)束時還蠻有用的,讓我們看看Golang如何改善上述范例。
這東西很邪惡,不是嗎?又不是在寫B(tài)ASIC,不過也許有時候你會在PHP 用上呢。但是拜托,不要。
Golang中僅有 for 一種回圈但卻能夠達(dá)成 foreach 、 while 、 for 多種用法。普通 for 回圈寫法在兩個語言中都十分相近。
在Golang請記得:如果你的 i 先前并不存在,那么你就需要定義它,所以下面這個范例你會看見 i := 0 。
在PHP里, foreach() 能夠直接給你值和鍵名,用起來十分簡單。
Golang里面雖然僅有 for() 但卻可以使用 range 達(dá)成和PHP一樣的 foreach 方式。
一個 while(條件) 回圈在PHP里面可以不斷地執(zhí)行區(qū)塊中的程式,直到 條件 為 false 為止。
在Golang里也有相同的做法,但仍是透過 for 回圈,請注意這個 for 回圈并沒有任何的分號( ; ),而且一個沒有條件的 for 回圈會一直被執(zhí)行。
PHP中有 do .. while() 回圈可以先做區(qū)塊中的動作。
在Golang中則沒有相關(guān)函式,但是你可以透過一個無止盡的 for 回圈加上條件式來讓他結(jié)束回圈。
要是你真的希望完全符合像是PHP那樣的設(shè)計方式,或者你可以在Golang中使用很邪惡的 goto 。
在PHP中我們可以透過 date() 像這樣取得目前的日期。
在Golang就稍微有趣點了,因為Golang中并不是以 Y-m-d 這種格式做為定義,而是 1 、 2 、 3 ,這令你需要去翻閱文件,才能夠知道 1 的定義是代表什么。
俗話說:「爆炸就是藝術(shù)」,可愛的PHP用詞真的很大膽,像是: explode() (爆炸)、 die() (死掉),回歸正傳,如果你想在PHP里面將字串切割成陣列,你可以這么做。
簡單的就讓一個字串給「爆炸」了,那么Golang 呢?
對了,記得引用 strings 套件。
這真的是很常用到的功能,就像物件一樣有著鍵名和鍵值,在PHP 里面你很簡單的就能靠陣列(Array)辦到。
真是太棒了,那么Golang呢?用 map 是差不多啦。如果有必要的話,你可以稍微復(fù)習(xí)一下先前提到的「多資料儲存型態(tài)-Stores」。
你很常會在PHP里面用 isset() 檢查一個索引是否存在,不是嗎?
在Golang里面很簡單的能夠這樣辦到(僅適用于 map )。
指針(有時也做參照)是一個像是「變數(shù)別名」的方法,這種方法讓你不用整天覆蓋舊的變數(shù),讓我們假設(shè) A = 1; B = A; 這個時候 B 會復(fù)制一份 A 且兩者不相干,倘若你希望修改 B 的時候?qū)嶋H上也會修改到 A 的值,就會需要指針。
指針比起復(fù)制一個變數(shù),他會建立一個指向到某個變數(shù)的記憶體位置,這也就是為什么你改變指針,實際上是在改變某個變數(shù)。
在Golang你需要用上 * 還有 符號。
有些時候你會回傳一個陣列,這個陣列里面可能有資料還有錯誤代號,而你會用條件式判斷錯誤代號是否非空值。
在Golang中函式可以一次回傳多個值。為此,你不需要真的回傳一個陣列,不過要注意的是你將會回傳一個屬于 error 資料型態(tài)的錯誤,所以你需要引用 errors 套件來幫助你做這件事。
該注意的是Golang沒有 try .. catch ,因為 Golang推薦這種錯誤處理方式 ,你應(yīng)該在每一次執(zhí)行可能會發(fā)生錯誤的程式時就處理錯誤,而非后來用 try 到處包覆你的程式。
在 if 條件式里宣告變數(shù)會讓你只能在 if 內(nèi)部使用這個變數(shù),而不會污染到全域范圍。
也許你在PHP中更常用的會是 try .. catch ,在大型商業(yè)邏輯時經(jīng)常看見如此地用法,實際上這種用法令人感到聒噪(因為你會需要一堆 try 區(qū)塊):
Golang中并沒有 try .. catch ,實際上Golang也 不鼓勵這種行為 (Golang推薦逐一處理錯誤的方式),倘若你真想辦倒像是捕捉異常這樣的方式,你確實可以使用Golang中另類處理錯誤的方式(可以的話盡量避免使用這種方式): panic() , recover() , defer 。
你可以把 panic() 當(dāng)作是 throw (丟出錯誤),而這跟PHP的 exit() 有87%像,一但你執(zhí)行了 panic() 你的程式就會宣告而終,但是別擔(dān)心,因為程式結(jié)束的時候會呼叫 defer ,所以我們接下來要在 defer 停止 panic() 。
關(guān)于 defer 上述已經(jīng)有提到了,他是一個反向執(zhí)行的宣告,會在函式結(jié)束后被執(zhí)行,當(dāng)你呼叫了 panic() 結(jié)束程式的時候,也就會開始執(zhí)行 defer ,所以我們要在 defer 內(nèi)使用 recover() 讓程式不再繼續(xù)進(jìn)行結(jié)束動作,這就像是捕捉異常。
recover() 可以看作 catch (捕捉),我們要在 defer 里面用 recover() 解決 panic() ,如此一來程式就會回歸正常而不會被結(jié)束。
還記得在PHP里要引用一堆檔案的日子嗎?到處可見的 require() 或是 include() ?到了Golang這些都不見了,取而代之的是「套件(Package)」?,F(xiàn)在讓我們來用PHP解釋一下。
這看起來很正常對吧?但假設(shè)你有一堆檔案,這馬上就成了 Include Hell ,讓我們看看Golang怎么透過「套件」解決這個問題。
「 蛤???殺????? 」你可能如此地說道。是的, main.go 中除了引用 fmt 套件( 為了要輸出結(jié)果用的套件 )之外完全沒有引用到 a.go 。
「 蛤???殺???????? 」你仿佛回到了幾秒鐘前的自己。
既然沒有引用其他檔案,為什么 main.go 可以輸出 foo 呢?注意到了嗎, 兩者都是屬于 main 套件 ,因此 他們共享同一個區(qū)域 ,所以接下來要介紹的是什么叫做「套件」。
套件是每一個 .go 檔案都必須聲明在Golang原始碼中最開端的東西,像下面這樣:
這意味著目前的檔案是屬于 main 套件( 你也可以依照你的喜好命名 ),那么要如何讓同個套件之間的函式溝通呢?
接著是Golang;注意!你不需要引用任何檔案,因為下列兩個檔案同屬一個套件。
一個由「套件」所掌握的世界,比起PHP的 include() 和 require() 還要好太多了,對嗎?
在Golang 中沒有引用單獨檔案的方式,你必須匯入一整個套件,而且你要記?。骸敢欢銋R入了,你就一定要使用它」,像下面這樣。
假如你不希望使用你匯入的套件,你只是為了要觸發(fā)那個套件的 main() 函式而引用的話??,那么你可以在前面加上一個底線( _ )。
如果你的套件出現(xiàn)了名稱沖突,你可以在套件來源前面給他一個新的名稱。
現(xiàn)在你知道可以匯入套件了,那么什么是「匯出」?同個套件內(nèi)的函式還有共享變數(shù)確實可以直接用,但那 并不表示可以給其他套件使用 ,其方法取決于 函式/變數(shù)的「開頭大小寫」 。
是的。 Golang依照一個函式/變數(shù)的開頭大小寫決定這個東西是否可供「匯出」 。
這用在區(qū)別函式的時候格外有用,因為小寫開頭的任何事物都是不供匯出的,反之,大寫開頭的任何事物都是用來匯出供其他套件使用的。
一開始可能會覺得這是什么奇異的規(guī)定,但寫久之后,你就能發(fā)現(xiàn)比起JavaScript和Python以「底線為開頭的命名方式」還要來得更好;比起成天宣告 public 、 private 、 protected 還要來得更快。
在Golang 中沒有類別,但有所謂的「建構(gòu)體(Struct)」和「接口(Interface)」,這就能夠滿足幾乎所有的需求了,這也是為什么我認(rèn)為Golang 很簡潔卻又很強(qiáng)大的原因。
讓我們先用PHP 建立一個類別,然后看看Golang 怎么解決這個問題。
雖然Golang沒有類別,但是「建構(gòu)體(Struct)」就十分地堪用了,首先你要知道在Golang中「類別」的成員還有方法都是在「類別」外面所定義的,這跟PHP在類別內(nèi)定義的方式有所不同,在Golang中還有一點,那就是他們沒有 public 、 private 、 protected 的種類。
在PHP中,當(dāng)有一個類別被 new 的時候會自動執(zhí)行該類別內(nèi)的建構(gòu)子( __construct() ),通常你會用這個來初始化一些類別內(nèi)部的值。
但是在Golang 里因為沒有類別,也就沒有建構(gòu)子,不巧的是建構(gòu)體本身也不帶有建構(gòu)子的特性,這個時候你只能自己在外部建立一個建構(gòu)用函式。
讓我們假設(shè)你有兩個類別,你會把其中一個類別傳入到另一個類別里面使用,廢話不多說!先上個PHP 范例(為了簡短篇幅我省去了換行)。
在Golang中你也有相同的用法,但是請記得:「 任何東西都是在「類別」外完成建構(gòu)的 」。
在PHP 中沒有相關(guān)的范例,這部分會以剛才「嵌入」章節(jié)中的Golang 范例作為解說對象。
你可以看見Golang在進(jìn)行 Foo 嵌入 Bar 的時候,會自動將 Foo 的成員暴露在 Bar 底下,那么假設(shè)「雙方之間有相同的成員名稱」呢?
這個時候被嵌入的成員就會被「遮蔽」,下面是個實際范例,還有你如何解決遮蔽問題:
雖然都是呼叫同一個函式,但是這個函式可以針對不同的資料來源做出不同的舉動,這就是多形。你也能夠把這看作是:「訊息的意義由接收者定義,而不是傳送者」。
目前PHP 中沒有真正的「多形」,不過你仍可以做出同樣的東西。
嗯??那么Golang呢?實際上更簡單而且更有條理了,在Golang中有 interface 可以幫忙完成這個工作。
如果你對Interface還不熟悉,可以試著查看「 解釋Golang中的Interface到底是什么 」文章。
謝謝你看到這里,可惜這篇文章卻沒有說出Golang 最重要的賣點:「Goroutine」和「Channel」
作為一個測試,作為一個測試開發(fā), 全?;?管理 是我們未來的發(fā)展方向。已經(jīng)掌握了Java、Python、HTML的你,是不是也想了解下最近異?;鸨腉o語言呢?來吧,讓我們一起了解下。
Go 是一個開源的編程語言 ,它能讓構(gòu)造簡單、可靠且高效的軟件變得容易。
Go是從2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持開發(fā),后來還加入了Ian Lance Taylor, Russ Cox等人,并最終于2009年11月開源,在2012年早些時候發(fā)布了Go 1穩(wěn)定版本?,F(xiàn)在Go的開發(fā)已經(jīng)是完全開放的,并且擁有一個活躍的社區(qū)。這三個人都是計算機(jī)界的大神,有的參與了C語言的編寫,有的還是數(shù)學(xué)大神,有的還獲得了計算機(jī)最高榮譽-圖靈獎。
接下來說說 Go語言的特色 :
簡潔、快速、安全
并行、有趣、開源
內(nèi)存管理、數(shù)組安全、編譯迅速
Go語言的用途 :
Go 語言被設(shè)計成一門應(yīng)用于搭載 Web 服務(wù)器,存儲集群或類似用途的巨型中央服務(wù)器的系統(tǒng)編程語言。
對于高性能分布式系統(tǒng)領(lǐng)域而言,Go 語言無疑比大多數(shù)其它語言有著更高的開發(fā)效率。它提供了海量并行的支持,這對于 游戲 服務(wù)端的開發(fā)而言是再好不過了。
Go語言的環(huán)境安裝:
建議直接打開 官方地址因為墻的原因打不開
因為我用的是windows系統(tǒng),這里主要講下Windows系統(tǒng)上使用Go語言來編程。
Windows 下可以使用 .msi 后綴(在下載列表中可以找到該文件,如go1.17.2.windows-amd64.msi)的安裝包來安裝。
默認(rèn)情況下 .msi 文件會安裝在 c:Go 目錄下。你可以將 c:Gobin 目錄添加到 Path 環(huán)境變量中。添加后你需要重啟命令窗口才能生效。個人建議還是安裝到 Program Files文件夾中。
使用什么開發(fā)工具來對Go語言進(jìn)行編寫:
個人建議用VS code, 也可以用Sublime Text來編輯。如果你之前看了我講的HTML語言的學(xué)習(xí),肯定已經(jīng)下載了VS code. 那么這時你需要在VS code中下載Go語言的擴(kuò)展插件。
這里有一個巨大的坑,就是在下載Go的插件和依賴包時,會提示一些包沒有。主要是因為下載的依賴包部分被墻了,只能想別的辦法去下載。
建議參考網(wǎng)頁:
解決vscode中g(shù)olang插件安裝失敗方法
在學(xué)習(xí)go的過程中,使用的是vscode,但是一直提示安裝相關(guān)插件失敗,然后上網(wǎng)查方法,基本上是叫你建立golang.org目錄什么的,結(jié)果全是錯的,而且都是抄襲,很煩。無意之中看到一位博主分享的方法,他也是飽受上述的垃圾博文困擾,然后找到了解決方法,這里向他致敬,秉著讓更多人看到正確解決方法的心,我寫下正確的解決方法,希望對你有所幫助,也可以點開原博主鏈接參考:
Go有一個全球模塊代理,設(shè)置代理再去安裝golang的插件,就可以安裝成功了。步驟有,首先Windows用戶打開Powershell,一個藍(lán)色的界面,注意不是cmd!不知道的直接打開window下面的搜索,然后輸入powershell,搜索出來就可以了。
$env:GO111MODULE=“on”
$env:GOPROXY=“”
go env -w GOPROXY=
go env -w GOPRIVATE=*.corp.example.com
然后我們打開VsCode界面,下面會提示安裝插件,我們選擇Install ALL,就會安裝成功
當(dāng)你在運行Go語言程序時,提示所有的插件包都已經(jīng)安裝成功了時,就可以正常使用了,要不然一堆報錯會讓你非常心煩。
好了,今天先到這里,晚安、下班~
golang學(xué)習(xí)筆記
頻繁創(chuàng)建線程會造成不必要的開銷,所以才有了線程池。在線程池中預(yù)先保存一定數(shù)量的線程,新任務(wù)發(fā)布到任務(wù)隊列,線程池中的線程不斷地從任務(wù)隊列中取出任務(wù)并執(zhí)行,可以有效的減少創(chuàng)建和銷毀帶來的開銷。
過多的線程會導(dǎo)致爭搶cpu資源,且上下文的切換的開銷變大。而工作在用戶態(tài)的協(xié)程能大大減少上下文切換的開銷。協(xié)程調(diào)度器把可運行的協(xié)程逐個調(diào)度到線程中執(zhí)行,同時即時把阻塞的協(xié)程調(diào)度出協(xié)程,從而有效地避免了線程的頻繁切換,達(dá)到了少量線程實現(xiàn)高并發(fā)的效果。
多個協(xié)程分享操作系統(tǒng)分給線程的時間片,從而達(dá)到充分利用CPU的目的,協(xié)程調(diào)度器決定了則決定了協(xié)程運行的順序。每個線程同一時刻只能運行一個協(xié)程。
go調(diào)度模型包含三個實體:
每個處理器維護(hù)者一個協(xié)程G的隊列,處理器依次將協(xié)程G調(diào)度到M中執(zhí)行。
每個P會周期性地查看全局隊列中是否有G待運行并將其調(diào)度到M中執(zhí)行,全局隊列中的G主要來自系統(tǒng)調(diào)用中恢復(fù)的G.
如果協(xié)程發(fā)起系統(tǒng)調(diào)用,則整個工作線程M被阻塞,協(xié)程隊列中的其他協(xié)程都會阻塞。
一般情況下M的個數(shù)會略大于P個數(shù),多出來的M將會在G產(chǎn)生系統(tǒng)調(diào)用時發(fā)揮作用。與線程池類似,Go也提供M池子。當(dāng)協(xié)程G1發(fā)起系統(tǒng)掉用時,M1會釋放P,由 M1-P-G1 G2 ... 轉(zhuǎn)變成 M1-G1 , M2會接管P的其他協(xié)程 M2-P-G2 G3 G4... 。
冗余的M可能來源于緩存池,也可能是新建的。
當(dāng)G1結(jié)束系統(tǒng)調(diào)用后,根據(jù)M1是否獲取到P,進(jìn)行不用的處理。
多個處理P維護(hù)隊列可能不均衡,導(dǎo)致部分處理器非常繁忙,而其余相對空閑。產(chǎn)生原因是有些協(xié)程自身不斷地派生協(xié)程。
為此Go調(diào)度器提供了工作量竊取策略,當(dāng)某個處理器P沒有需要調(diào)度的協(xié)程時,將從其他處理中偷取協(xié)程,每次偷取一半。
搶占式調(diào)度,是指避免某個協(xié)程長時間執(zhí)行,而阻礙其他協(xié)程被調(diào)度的機(jī)制。
調(diào)度器監(jiān)控每個協(xié)程執(zhí)行時間,一旦執(zhí)行時間過長且有其他協(xié)程等待,會把協(xié)程暫停,轉(zhuǎn)而調(diào)度等待的協(xié)程,以達(dá)到類似時間片輪轉(zhuǎn)的效果。比如for循環(huán)會一直占用執(zhí)行權(quán)。
在IO密集型應(yīng)用,GOMAXPROCS大小設(shè)置大一些,獲取性能會更好。
IO密集型會經(jīng)常發(fā)生系統(tǒng)調(diào)用,會有一個新的M啟用或創(chuàng)建,但由于Go調(diào)度器檢測M到被阻塞有一定延遲。如果P數(shù)量多,則P管理協(xié)程隊列會變小。
本文是對 Gopher 2017 中一個非常好的 Talk?: [Understanding Channel](GopherCon 2017: Kavya Joshi - Understanding Channels) 的學(xué)習(xí)筆記,希望能夠通過對 channel 的關(guān)鍵特性的理解,進(jìn)一步掌握其用法細(xì)節(jié)以及 Golang 語言設(shè)計哲學(xué)的管窺蠡測。
channel 是可以讓一個 goroutine 發(fā)送特定值到另一個 gouroutine 的通信機(jī)制。
原生的 channel 是沒有緩存的(unbuffered channel),可以用于 goroutine 之間實現(xiàn)同步。
關(guān)閉后不能再寫入,可以讀取直到 channel 中再沒有數(shù)據(jù),并返回元素類型的零值。
gopl/ch3/netcat3
首先從 channel 是怎么被創(chuàng)建的開始:
在 heap 上分配一個 hchan 類型的對象,并將其初始化,然后返回一個指向這個 hchan 對象的指針。
理解了 channel 的數(shù)據(jù)結(jié)構(gòu)實現(xiàn),現(xiàn)在轉(zhuǎn)到 channel 的兩個最基本方法: sends 和 receivces ,看一下以上的特性是如何體現(xiàn)在 sends 和 receives 中的:
假設(shè)發(fā)送方先啟動,執(zhí)行 ch - task0 :
如此為 channel 帶來了 goroutine-safe 的特性。
在這樣的模型里, sender goroutine - channel - receiver goroutine 之間, hchan 是唯一的共享內(nèi)存,而這個唯一的共享內(nèi)存又通過 mutex 來確保 goroutine-safe ,所有在隊列中的內(nèi)容都只是副本。
這便是著名的 golang 并發(fā)原則的體現(xiàn):
發(fā)送方 goroutine 會阻塞,暫停,并在收到 receive 后才恢復(fù)。
goroutine 是一種 用戶態(tài)線程 , 由 Go runtime 創(chuàng)建并管理,而不是操作系統(tǒng),比起操作系統(tǒng)線程來說,goroutine更加輕量。
Go runtime scheduler 負(fù)責(zé)將 goroutine 調(diào)度到操作系統(tǒng)線程上。
runtime scheduler 怎么將 goroutine 調(diào)度到操作系統(tǒng)線程上?
當(dāng)阻塞發(fā)生時,一次 goroutine 上下文切換的全過程:
然而,被阻塞的 goroutine 怎么恢復(fù)過來?
阻塞發(fā)生時,調(diào)用 runtime sheduler 執(zhí)行 gopark 之前,G1 會創(chuàng)建一個 sudog ,并將它存放在 hchan 的 sendq 中。 sudog 中便記錄了即將被阻塞的 goroutine G1 ,以及它要發(fā)送的數(shù)據(jù)元素 task4 等等。
接收方 將通過這個 sudog 來恢復(fù) G1
接收方 G2 接收數(shù)據(jù), 并發(fā)出一個 receivce ,將 G1 置為 runnable :
同樣的, 接收方 G2 會被阻塞,G2 會創(chuàng)建 sudoq ,存放在 recvq ,基本過程和發(fā)送方阻塞一樣。
不同的是,發(fā)送方 G1如何恢復(fù)接收方 G2,這是一個非常神奇的實現(xiàn)。
理論上可以將 task 入隊,然后恢復(fù) G2, 但恢復(fù) G2后,G2會做什么呢?
G2會將隊列中的 task 復(fù)制出來,放到自己的 memory 中,基于這個思路,G1在這個時候,直接將 task 寫到 G2的 stack memory 中!
這是違反常規(guī)的操作,理論上 goroutine 之間的 stack 是相互獨立的,只有在運行時可以執(zhí)行這樣的操作。
這么做純粹是出于性能優(yōu)化的考慮,原來的步驟是:
優(yōu)化后,相當(dāng)于減少了 G2 獲取鎖并且執(zhí)行 memcopy 的性能消耗。
channel 設(shè)計背后的思想可以理解為 simplicity 和 performance 之間權(quán)衡抉擇,具體如下:
queue with a lock prefered to lock-free implementation:
比起完全 lock-free 的實現(xiàn),使用鎖的隊列實現(xiàn)更簡單,容易實現(xiàn)