———文章來(lái)源 YamiOdymel/PHP-to-Golang
創(chuàng)新互聯(lián)建站主營(yíng)平泉網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營(yíng)網(wǎng)站建設(shè)方案,App定制開發(fā),平泉h5重慶小程序開發(fā)搭建,平泉網(wǎng)站營(yíng)銷推廣歡迎平泉等地區(qū)企業(yè)咨詢
PHP和模塊之間的關(guān)系令人感到煩躁,假設(shè)你要讀取 yaml 檔案,你需要有一個(gè) yaml 的模塊,為此,你還需要將其編譯然后將編譯后的模塊擺放至指定位置,之后換了一臺(tái)伺服器你還要重新編譯,這點(diǎn)到現(xiàn)在還是沒(méi)有改善;順帶一提之后出了PHP 7效能確實(shí)提升了許多(比Python 3快了些),但PHP仍令我感到臃腫,我覺得是時(shí)候
(轉(zhuǎn)行)了。
PHP 和Golang 的效能我想毋庸置疑是后者比較快(而且是以倍數(shù)來(lái)算),也許有的人會(huì)認(rèn)為兩種不應(yīng)該被放在一起比較,但Golang 本身就是偏向Web 開發(fā)的,所以這也是為什么我考慮轉(zhuǎn)用Golang 的原因,起初我的考慮有幾個(gè):Node.js 和Rust 還有最終被選定的Golang;先談?wù)凬ode.js 吧。
Node.js的效能可以說(shuō)是快上PHP 3.5倍至6倍左右 ,而且撰寫的語(yǔ)言還是JavaScript,蒸蚌,如此一來(lái)就不需要學(xué)習(xí)新語(yǔ)言了!搭配Babel更可以說(shuō)是萬(wàn)能,不過(guò)那跟「跳跳虎」一樣的Async邏輯還有那恐怖的Callback Hell,有人認(rèn)為前者是種優(yōu)點(diǎn),這點(diǎn)我不否認(rèn),但是對(duì)學(xué)習(xí)PHP的我來(lái)說(shuō)太過(guò)于"Mind Fuck",至于后者的Callback Hell雖然有Promise,但是那又是另一個(gè)「Then Hell」的故事了。相較于Golang之下,Node.js似乎就沒(méi)有那么吸引我了。你確實(shí)可以用Node.js寫出很多東西,不過(guò)那V8引擎的效能仍然有限,而且要學(xué)習(xí)新的事物,不就應(yīng)該是「全新」的嗎;)?
題外話: 為什么Node.js不適合大型和商業(yè)專案?
在拋棄改用Node.js 之后我曾經(jīng)花了一天的時(shí)間嘗試Rust 和Iron 框架,嗯??Rust 太強(qiáng)大了,強(qiáng)大到讓我覺得Rust 不應(yīng)該用在這里,這想法也許很蠢,但Rust 讓我覺得適合更應(yīng)該拿來(lái)用在系統(tǒng)或者是部分底層的地方,而不應(yīng)該是網(wǎng)路服務(wù)。
Golang是我最終的選擇,主要在于我花了一天的時(shí)間來(lái)研究的時(shí)候意外地發(fā)現(xiàn)Golang夭壽簡(jiǎn)潔( 關(guān)鍵字只有25個(gè) ),相較之下Rust太過(guò)于「強(qiáng)大」令我怯步;而且Golang帶有許多工具,例如 go fmt 會(huì)自動(dòng)幫你整理程式碼、 go doc 會(huì)自動(dòng)幫你生產(chǎn)文件、 go test 可以自動(dòng)單元測(cè)試并生產(chǎn)覆蓋率報(bào)表、也有 go get 套件管理工具(雖然沒(méi)有版本功能),不過(guò)都很實(shí)用,而且也不需要加上分號(hào)( ; ),真要說(shuō)不好的地方??大概就是強(qiáng)迫你花括號(hào)不能換行放吧(沒(méi)錯(cuò),我就是花括號(hào)會(huì)換行放的人)。
當(dāng)我在撰寫這份文件的時(shí)候 我會(huì)先假設(shè)你有一定的基礎(chǔ) ,你可以先閱讀下列的手冊(cè),他們都很不錯(cuò)。
你能夠在PHP 里面想建立一個(gè)變數(shù)的時(shí)候就直接建立,夭壽贊,是嗎?
蒸蚌!那么Golang 呢?在Golang 中變數(shù)分為幾類:「新定義」、「預(yù)先定義」、「自動(dòng)新定義」、「覆蓋」。讓我們來(lái)看看范例:
在PHP中你會(huì)很常用到 echo 來(lái)顯示文字,像這樣。
然而在Golang中你會(huì)需要 fmt 套件,關(guān)于「什么是套件」的說(shuō)明你可以在文章下述了解。
這很簡(jiǎn)單,而且兩個(gè)語(yǔ)言的用法相差甚少,下面這是PHP:
只是Golang 稍微聒噪了一點(diǎn),你必須在函式后面宣告他最后會(huì)回傳什么資料型別。
在PHP 中你要回傳多個(gè)資料你就會(huì)用上陣列,然后將資料放入陣列里面,像這樣。
然而在Golang 中你可以不必用到一個(gè)陣列,函式可以一次回傳多個(gè)值:
兩個(gè)語(yǔ)言的撰寫方式不盡相同。
主要是PHP 的陣列能做太多事情了,所以在PHP 里面要儲(chǔ)存什么用陣列就好了。
在Golang里??沒(méi)有這么萬(wàn)能的東西,首先要先了解Golang中有這些型態(tài): array , slice , map , interface ,
你他媽的我到底看了三洨,首先你要知道Golang是個(gè)強(qiáng)型別語(yǔ)言,意思是你的陣列中 只能有一種型態(tài) ,什么意思?當(dāng)你決定這個(gè)陣列是用來(lái)擺放字串資料的時(shí)候,你就只能在里面放字串。沒(méi)有數(shù)值、沒(méi)有布林值,就像你沒(méi)有女朋友一樣。
先撇開PHP 的「萬(wàn)能陣列」不管,Golang 中的陣列既單純卻又十分腦殘,在定義一個(gè)陣列的時(shí)候,你必須給他一個(gè)長(zhǎng)度還有其內(nèi)容存放的資料型態(tài),你的陣列內(nèi)容不一定要填滿其長(zhǎng)度,但是你的陣列內(nèi)容不能超過(guò)你當(dāng)初定義的長(zhǎng)度。
切片??這聽起來(lái)也許很奇怪,但是你確實(shí)可以「切」他,讓我們先談?wù)劇盖衅贡绕稹戈嚵小挂迷谀睦铮骸改悴挥枚x其最大長(zhǎng)度,而且你可以直接賦予值」,沒(méi)了。
我們剛才有提到你可以「切」他,記得嗎?這有點(diǎn)像是PHP中的 array_slice() ,但是Golang直接讓Slice「內(nèi)建」了這個(gè)用法,其用法是: slice[開始:結(jié)束] 。
在PHP中倒是沒(méi)有那么方便,在下列PHP范例中你需要不斷地使用 array_slice() 。
你可以把「映照」看成是一個(gè)有鍵名和鍵值的陣列,但是記?。骸改阈枰孪榷x其鍵名、鍵值的資料型態(tài)」,這仍限制你沒(méi)辦法在映照中存放多種不同型態(tài)的資料。
在Golang里可就沒(méi)這么簡(jiǎn)單了,你需要先用 make() 宣告 map 。
也許你不喜歡「接口」這個(gè)詞,但用「介面」我怕會(huì)誤導(dǎo)大眾,所以,是的,接下來(lái)我會(huì)繼續(xù)稱其為「接口」。還記得你可以在PHP 的關(guān)聯(lián)陣列里面存放任何型態(tài)的資料嗎,像下面這樣?
現(xiàn)在你有福了!正因?yàn)镚olang中的 interface{} 可以接受任何內(nèi)容,所以你可以把它拿來(lái)存放任何型態(tài)的資料。
有時(shí)候你也許會(huì)有個(gè)不定值的變數(shù),在PHP 里你可以直接將一個(gè)變數(shù)定義成字串、數(shù)值、空值、就像你那變心的女友一樣隨時(shí)都在變。
在Golang中你必須給予變數(shù)一個(gè)指定的資料型別,不過(guò)還記得剛才提到的:「Golang中有個(gè) interface{} 能夠 存放任何事物 」嗎( 雖然也不是真的任何事物啦?? )?
當(dāng)我們程式中不需要繼續(xù)使用到某個(gè)資源或是發(fā)生錯(cuò)誤的時(shí)候,我們索性會(huì)將其關(guān)閉或是拋棄來(lái)節(jié)省資源開銷,例如PHP 里的讀取檔案:
在Golang中,你可以使用 defer 來(lái)在函式結(jié)束的時(shí)候自動(dòng)執(zhí)行某些程式(其執(zhí)行方向?yàn)榉聪?。所以你就不需要在函式最后面結(jié)束最前面的資源。
defer 可以被稱為「推遲執(zhí)行」,實(shí)際上就是在函式結(jié)束后會(huì)「反序」執(zhí)行的東西,例如你按照了這樣的順序定義 defer : A-B-C-D ,那么執(zhí)行的順序其實(shí)會(huì)是 D-C-B-A ,這用在程式結(jié)束時(shí)還蠻有用的,讓我們看看Golang如何改善上述范例。
這東西很邪惡,不是嗎?又不是在寫B(tài)ASIC,不過(guò)也許有時(shí)候你會(huì)在PHP 用上呢。但是拜托,不要。
Golang中僅有 for 一種回圈但卻能夠達(dá)成 foreach 、 while 、 for 多種用法。普通 for 回圈寫法在兩個(gè)語(yǔ)言中都十分相近。
在Golang請(qǐng)記得:如果你的 i 先前并不存在,那么你就需要定義它,所以下面這個(gè)范例你會(huì)看見 i := 0 。
在PHP里, foreach() 能夠直接給你值和鍵名,用起來(lái)十分簡(jiǎn)單。
Golang里面雖然僅有 for() 但卻可以使用 range 達(dá)成和PHP一樣的 foreach 方式。
一個(gè) while(條件) 回圈在PHP里面可以不斷地執(zhí)行區(qū)塊中的程式,直到 條件 為 false 為止。
在Golang里也有相同的做法,但仍是透過(guò) for 回圈,請(qǐng)注意這個(gè) for 回圈并沒(méi)有任何的分號(hào)( ; ),而且一個(gè)沒(méi)有條件的 for 回圈會(huì)一直被執(zhí)行。
PHP中有 do .. while() 回圈可以先做區(qū)塊中的動(dòng)作。
在Golang中則沒(méi)有相關(guān)函式,但是你可以透過(guò)一個(gè)無(wú)止盡的 for 回圈加上條件式來(lái)讓他結(jié)束回圈。
要是你真的希望完全符合像是PHP那樣的設(shè)計(jì)方式,或者你可以在Golang中使用很邪惡的 goto 。
在PHP中我們可以透過(guò) date() 像這樣取得目前的日期。
在Golang就稍微有趣點(diǎn)了,因?yàn)镚olang中并不是以 Y-m-d 這種格式做為定義,而是 1 、 2 、 3 ,這令你需要去翻閱文件,才能夠知道 1 的定義是代表什么。
俗話說(shuō):「爆炸就是藝術(shù)」,可愛的PHP用詞真的很大膽,像是: explode() (爆炸)、 die() (死掉),回歸正傳,如果你想在PHP里面將字串切割成陣列,你可以這么做。
簡(jiǎn)單的就讓一個(gè)字串給「爆炸」了,那么Golang 呢?
對(duì)了,記得引用 strings 套件。
這真的是很常用到的功能,就像物件一樣有著鍵名和鍵值,在PHP 里面你很簡(jiǎn)單的就能靠陣列(Array)辦到。
真是太棒了,那么Golang呢?用 map 是差不多啦。如果有必要的話,你可以稍微復(fù)習(xí)一下先前提到的「多資料儲(chǔ)存型態(tài)-Stores」。
你很常會(huì)在PHP里面用 isset() 檢查一個(gè)索引是否存在,不是嗎?
在Golang里面很簡(jiǎn)單的能夠這樣辦到(僅適用于 map )。
指針(有時(shí)也做參照)是一個(gè)像是「變數(shù)別名」的方法,這種方法讓你不用整天覆蓋舊的變數(shù),讓我們假設(shè) A = 1; B = A; 這個(gè)時(shí)候 B 會(huì)復(fù)制一份 A 且兩者不相干,倘若你希望修改 B 的時(shí)候?qū)嶋H上也會(huì)修改到 A 的值,就會(huì)需要指針。
指針比起復(fù)制一個(gè)變數(shù),他會(huì)建立一個(gè)指向到某個(gè)變數(shù)的記憶體位置,這也就是為什么你改變指針,實(shí)際上是在改變某個(gè)變數(shù)。
在Golang你需要用上 * 還有 符號(hào)。
有些時(shí)候你會(huì)回傳一個(gè)陣列,這個(gè)陣列里面可能有資料還有錯(cuò)誤代號(hào),而你會(huì)用條件式判斷錯(cuò)誤代號(hào)是否非空值。
在Golang中函式可以一次回傳多個(gè)值。為此,你不需要真的回傳一個(gè)陣列,不過(guò)要注意的是你將會(huì)回傳一個(gè)屬于 error 資料型態(tài)的錯(cuò)誤,所以你需要引用 errors 套件來(lái)幫助你做這件事。
該注意的是Golang沒(méi)有 try .. catch ,因?yàn)?Golang推薦這種錯(cuò)誤處理方式 ,你應(yīng)該在每一次執(zhí)行可能會(huì)發(fā)生錯(cuò)誤的程式時(shí)就處理錯(cuò)誤,而非后來(lái)用 try 到處包覆你的程式。
在 if 條件式里宣告變數(shù)會(huì)讓你只能在 if 內(nèi)部使用這個(gè)變數(shù),而不會(huì)污染到全域范圍。
也許你在PHP中更常用的會(huì)是 try .. catch ,在大型商業(yè)邏輯時(shí)經(jīng)??匆娙绱说赜梅ǎ瑢?shí)際上這種用法令人感到聒噪(因?yàn)槟銜?huì)需要一堆 try 區(qū)塊):
Golang中并沒(méi)有 try .. catch ,實(shí)際上Golang也 不鼓勵(lì)這種行為 (Golang推薦逐一處理錯(cuò)誤的方式),倘若你真想辦倒像是捕捉異常這樣的方式,你確實(shí)可以使用Golang中另類處理錯(cuò)誤的方式(可以的話盡量避免使用這種方式): panic() , recover() , defer 。
你可以把 panic() 當(dāng)作是 throw (丟出錯(cuò)誤),而這跟PHP的 exit() 有87%像,一但你執(zhí)行了 panic() 你的程式就會(huì)宣告而終,但是別擔(dān)心,因?yàn)槌淌浇Y(jié)束的時(shí)候會(huì)呼叫 defer ,所以我們接下來(lái)要在 defer 停止 panic() 。
關(guān)于 defer 上述已經(jīng)有提到了,他是一個(gè)反向執(zhí)行的宣告,會(huì)在函式結(jié)束后被執(zhí)行,當(dāng)你呼叫了 panic() 結(jié)束程式的時(shí)候,也就會(huì)開始執(zhí)行 defer ,所以我們要在 defer 內(nèi)使用 recover() 讓程式不再繼續(xù)進(jìn)行結(jié)束動(dòng)作,這就像是捕捉異常。
recover() 可以看作 catch (捕捉),我們要在 defer 里面用 recover() 解決 panic() ,如此一來(lái)程式就會(huì)回歸正常而不會(huì)被結(jié)束。
還記得在PHP里要引用一堆檔案的日子嗎?到處可見的 require() 或是 include() ?到了Golang這些都不見了,取而代之的是「套件(Package)」?,F(xiàn)在讓我們來(lái)用PHP解釋一下。
這看起來(lái)很正常對(duì)吧?但假設(shè)你有一堆檔案,這馬上就成了 Include Hell ,讓我們看看Golang怎么透過(guò)「套件」解決這個(gè)問(wèn)題。
「 蛤???殺????? 」你可能如此地說(shuō)道。是的, main.go 中除了引用 fmt 套件( 為了要輸出結(jié)果用的套件 )之外完全沒(méi)有引用到 a.go 。
「 蛤???殺小?????? 」你仿佛回到了幾秒鐘前的自己。
既然沒(méi)有引用其他檔案,為什么 main.go 可以輸出 foo 呢?注意到了嗎, 兩者都是屬于 main 套件 ,因此 他們共享同一個(gè)區(qū)域 ,所以接下來(lái)要介紹的是什么叫做「套件」。
套件是每一個(gè) .go 檔案都必須聲明在Golang原始碼中最開端的東西,像下面這樣:
這意味著目前的檔案是屬于 main 套件( 你也可以依照你的喜好命名 ),那么要如何讓同個(gè)套件之間的函式溝通呢?
接著是Golang;注意!你不需要引用任何檔案,因?yàn)橄铝袃蓚€(gè)檔案同屬一個(gè)套件。
一個(gè)由「套件」所掌握的世界,比起PHP的 include() 和 require() 還要好太多了,對(duì)嗎?
在Golang 中沒(méi)有引用單獨(dú)檔案的方式,你必須匯入一整個(gè)套件,而且你要記住:「一定你匯入了,你就一定要使用它」,像下面這樣。
假如你不希望使用你匯入的套件,你只是為了要觸發(fā)那個(gè)套件的 main() 函式而引用的話??,那么你可以在前面加上一個(gè)底線( _ )。
如果你的套件出現(xiàn)了名稱沖突,你可以在套件來(lái)源前面給他一個(gè)新的名稱。
現(xiàn)在你知道可以匯入套件了,那么什么是「匯出」?同個(gè)套件內(nèi)的函式還有共享變數(shù)確實(shí)可以直接用,但那 并不表示可以給其他套件使用 ,其方法取決于 函式/變數(shù)的「開頭大小寫」 。
是的。 Golang依照一個(gè)函式/變數(shù)的開頭大小寫決定這個(gè)東西是否可供「匯出」 。
這用在區(qū)別函式的時(shí)候格外有用,因?yàn)樾戦_頭的任何事物都是不供匯出的,反之,大寫開頭的任何事物都是用來(lái)匯出供其他套件使用的。
一開始可能會(huì)覺得這是什么奇異的規(guī)定,但寫久之后,你就能發(fā)現(xiàn)比起JavaScript和Python以「底線為開頭的命名方式」還要來(lái)得更好;比起成天宣告 public 、 private 、 protected 還要來(lái)得更快。
在Golang 中沒(méi)有類別,但有所謂的「建構(gòu)體(Struct)」和「接口(Interface)」,這就能夠滿足幾乎所有的需求了,這也是為什么我認(rèn)為Golang 很簡(jiǎn)潔卻又很強(qiáng)大的原因。
讓我們先用PHP 建立一個(gè)類別,然后看看Golang 怎么解決這個(gè)問(wèn)題。
雖然Golang沒(méi)有類別,但是「建構(gòu)體(Struct)」就十分地堪用了,首先你要知道在Golang中「類別」的成員還有方法都是在「類別」外面所定義的,這跟PHP在類別內(nèi)定義的方式有所不同,在Golang中還有一點(diǎn),那就是他們沒(méi)有 public 、 private 、 protected 的種類。
在PHP中,當(dāng)有一個(gè)類別被 new 的時(shí)候會(huì)自動(dòng)執(zhí)行該類別內(nèi)的建構(gòu)子( __construct() ),通常你會(huì)用這個(gè)來(lái)初始化一些類別內(nèi)部的值。
但是在Golang 里因?yàn)闆](méi)有類別,也就沒(méi)有建構(gòu)子,不巧的是建構(gòu)體本身也不帶有建構(gòu)子的特性,這個(gè)時(shí)候你只能自己在外部建立一個(gè)建構(gòu)用函式。
讓我們假設(shè)你有兩個(gè)類別,你會(huì)把其中一個(gè)類別傳入到另一個(gè)類別里面使用,廢話不多說(shuō)!先上個(gè)PHP 范例(為了簡(jiǎn)短篇幅我省去了換行)。
在Golang中你也有相同的用法,但是請(qǐng)記得:「 任何東西都是在「類別」外完成建構(gòu)的 」。
在PHP 中沒(méi)有相關(guān)的范例,這部分會(huì)以剛才「嵌入」章節(jié)中的Golang 范例作為解說(shuō)對(duì)象。
你可以看見Golang在進(jìn)行 Foo 嵌入 Bar 的時(shí)候,會(huì)自動(dòng)將 Foo 的成員暴露在 Bar 底下,那么假設(shè)「雙方之間有相同的成員名稱」呢?
這個(gè)時(shí)候被嵌入的成員就會(huì)被「遮蔽」,下面是個(gè)實(shí)際范例,還有你如何解決遮蔽問(wèn)題:
雖然都是呼叫同一個(gè)函式,但是這個(gè)函式可以針對(duì)不同的資料來(lái)源做出不同的舉動(dòng),這就是多形。你也能夠把這看作是:「訊息的意義由接收者定義,而不是傳送者」。
目前PHP 中沒(méi)有真正的「多形」,不過(guò)你仍可以做出同樣的東西。
嗯??那么Golang呢?實(shí)際上更簡(jiǎn)單而且更有條理了,在Golang中有 interface 可以幫忙完成這個(gè)工作。
如果你對(duì)Interface還不熟悉,可以試著查看「 解釋Golang中的Interface到底是什么 」文章。
謝謝你看到這里,可惜這篇文章卻沒(méi)有說(shuō)出Golang 最重要的賣點(diǎn):「Goroutine」和「Channel」
本文是對(duì) Gopher 2017 中一個(gè)非常好的 Talk?: [Understanding Channel](GopherCon 2017: Kavya Joshi - Understanding Channels) 的學(xué)習(xí)筆記,希望能夠通過(guò)對(duì) channel 的關(guān)鍵特性的理解,進(jìn)一步掌握其用法細(xì)節(jié)以及 Golang 語(yǔ)言設(shè)計(jì)哲學(xué)的管窺蠡測(cè)。
channel 是可以讓一個(gè) goroutine 發(fā)送特定值到另一個(gè) gouroutine 的通信機(jī)制。
原生的 channel 是沒(méi)有緩存的(unbuffered channel),可以用于 goroutine 之間實(shí)現(xiàn)同步。
關(guān)閉后不能再寫入,可以讀取直到 channel 中再?zèng)]有數(shù)據(jù),并返回元素類型的零值。
gopl/ch3/netcat3
首先從 channel 是怎么被創(chuàng)建的開始:
在 heap 上分配一個(gè) hchan 類型的對(duì)象,并將其初始化,然后返回一個(gè)指向這個(gè) hchan 對(duì)象的指針。
理解了 channel 的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn),現(xiàn)在轉(zhuǎn)到 channel 的兩個(gè)最基本方法: sends 和 receivces ,看一下以上的特性是如何體現(xiàn)在 sends 和 receives 中的:
假設(shè)發(fā)送方先啟動(dòng),執(zhí)行 ch - task0 :
如此為 channel 帶來(lái)了 goroutine-safe 的特性。
在這樣的模型里, sender goroutine - channel - receiver goroutine 之間, hchan 是唯一的共享內(nèi)存,而這個(gè)唯一的共享內(nèi)存又通過(guò) mutex 來(lái)確保 goroutine-safe ,所有在隊(duì)列中的內(nèi)容都只是副本。
這便是著名的 golang 并發(fā)原則的體現(xiàn):
發(fā)送方 goroutine 會(huì)阻塞,暫停,并在收到 receive 后才恢復(fù)。
goroutine 是一種 用戶態(tài)線程 , 由 Go runtime 創(chuàng)建并管理,而不是操作系統(tǒng),比起操作系統(tǒng)線程來(lái)說(shuō),goroutine更加輕量。
Go runtime scheduler 負(fù)責(zé)將 goroutine 調(diào)度到操作系統(tǒng)線程上。
runtime scheduler 怎么將 goroutine 調(diào)度到操作系統(tǒng)線程上?
當(dāng)阻塞發(fā)生時(shí),一次 goroutine 上下文切換的全過(guò)程:
然而,被阻塞的 goroutine 怎么恢復(fù)過(guò)來(lái)?
阻塞發(fā)生時(shí),調(diào)用 runtime sheduler 執(zhí)行 gopark 之前,G1 會(huì)創(chuàng)建一個(gè) sudog ,并將它存放在 hchan 的 sendq 中。 sudog 中便記錄了即將被阻塞的 goroutine G1 ,以及它要發(fā)送的數(shù)據(jù)元素 task4 等等。
接收方 將通過(guò)這個(gè) sudog 來(lái)恢復(fù) G1
接收方 G2 接收數(shù)據(jù), 并發(fā)出一個(gè) receivce ,將 G1 置為 runnable :
同樣的, 接收方 G2 會(huì)被阻塞,G2 會(huì)創(chuàng)建 sudoq ,存放在 recvq ,基本過(guò)程和發(fā)送方阻塞一樣。
不同的是,發(fā)送方 G1如何恢復(fù)接收方 G2,這是一個(gè)非常神奇的實(shí)現(xiàn)。
理論上可以將 task 入隊(duì),然后恢復(fù) G2, 但恢復(fù) G2后,G2會(huì)做什么呢?
G2會(huì)將隊(duì)列中的 task 復(fù)制出來(lái),放到自己的 memory 中,基于這個(gè)思路,G1在這個(gè)時(shí)候,直接將 task 寫到 G2的 stack memory 中!
這是違反常規(guī)的操作,理論上 goroutine 之間的 stack 是相互獨(dú)立的,只有在運(yùn)行時(shí)可以執(zhí)行這樣的操作。
這么做純粹是出于性能優(yōu)化的考慮,原來(lái)的步驟是:
優(yōu)化后,相當(dāng)于減少了 G2 獲取鎖并且執(zhí)行 memcopy 的性能消耗。
channel 設(shè)計(jì)背后的思想可以理解為 simplicity 和 performance 之間權(quán)衡抉擇,具體如下:
queue with a lock prefered to lock-free implementation:
比起完全 lock-free 的實(shí)現(xiàn),使用鎖的隊(duì)列實(shí)現(xiàn)更簡(jiǎn)單,容易實(shí)現(xiàn)
GolangCI-Lint是一個(gè)lint聚合器,它的速度很快,平均速度是gometalinter的5倍。它易于集成和使用,具有良好的輸出并且具有最小數(shù)量的誤報(bào)。而且它還支持go modules。最重要的是免費(fèi)開源。
方式一:使用命令: go get -u github.com/golangci/golangci-lint/cmd/golangci-lint
方式二:離線安裝:
可以通過(guò)命令golangci-lint help linters查看它支持的linters。你可以傳入?yún)?shù)-E/--enable來(lái)使某個(gè)linter可用,也可以使用-D/--disable參數(shù)來(lái)使某個(gè)linter不可用。
最近三年,在工作中使用go開發(fā)了不少服務(wù)。深感g(shù)o的便捷,以及它的runtime的復(fù)雜。我覺得需要定期的進(jìn)行總結(jié),因此決定寫這篇文章,也許更準(zhǔn)確的,應(yīng)該叫筆記。
最近終于解決了一個(gè)和cgo有關(guān)的問(wèn)題。這個(gè)問(wèn)題從發(fā)現(xiàn)到解決前后經(jīng)歷了接近4個(gè)月,當(dāng)然,和人手不足也有關(guān)系。而對(duì)于我個(gè)人而言,這個(gè)問(wèn)題其實(shí)歷時(shí)2年!這得從頭說(shuō)起。
在上一家公司的一個(gè)項(xiàng)目里,有一個(gè)服務(wù)做音視頻數(shù)據(jù)的提取,這個(gè)服務(wù)運(yùn)行在嵌入式設(shè)備TX2上。音視頻提取這一關(guān)鍵功能主要利用nvidia基于gstreamer開發(fā)的插件,這個(gè)插件可以發(fā)揮nvidia gpu的硬件解碼功能。當(dāng)時(shí)這個(gè)服務(wù)使用go和c混編的方式,問(wèn)題的癥狀是服務(wù)運(yùn)行一段時(shí)間后,不輸出音視頻數(shù)據(jù)。遺憾的是,由于疫情,項(xiàng)目停止,因此沒(méi)有機(jī)會(huì)繼續(xù)研究這個(gè)問(wèn)題。
時(shí)間來(lái)到去年底。當(dāng)前這個(gè)項(xiàng)目進(jìn)行壓力測(cè)試,發(fā)現(xiàn)關(guān)鍵的語(yǔ)音處理服務(wù)運(yùn)行一段時(shí)間后,會(huì)出現(xiàn)不拉流的情況,因此也沒(méi)有后續(xù)的結(jié)果輸出。癥狀和上一個(gè)項(xiàng)目非常像。雖然使用的第三方SDK不一樣,但同樣用了go和c混編的方式。一開始,焦點(diǎn)就放在go的運(yùn)行時(shí)上,覺得可能是go和c相互調(diào)用的方式不對(duì)。經(jīng)過(guò)合理猜測(cè),并用測(cè)試進(jìn)行驗(yàn)證后,發(fā)現(xiàn)問(wèn)題還是在第三方拉流的SDK上,它們的回調(diào)函數(shù)必須要快,否則有可能會(huì)阻塞它們的回調(diào)線程。當(dāng)然,在go調(diào)用c的時(shí)候,如果耗時(shí)比較長(zhǎng),會(huì)對(duì)go的運(yùn)行時(shí)造成一些副作用;在c回調(diào)go的時(shí)候,go的運(yùn)行時(shí)也有可能阻塞c的回調(diào)線程。但go的運(yùn)行時(shí)已經(jīng)比較成熟,因此我覺得它對(duì)這個(gè)問(wèn)題的貢獻(xiàn)不大。以上采用了假設(shè)-驗(yàn)證的方法,主要的原因還是第三方的拉流SDK不開源。在定位問(wèn)題的過(guò)程中,使用了gdb的gcore來(lái)生成堆棧;也搭建了灰度環(huán)境來(lái)進(jìn)行壓力測(cè)試,以及完善監(jiān)控,這些都是解決方法的一部分。
正是這一問(wèn)題,促使我更多的了解go的運(yùn)行時(shí)。而我看得越多,越覺得go的運(yùn)行時(shí)是一個(gè)龐大的怪物。因此,抱著能了解一點(diǎn)是一點(diǎn)的心態(tài),不斷的完善這篇筆記。
新聞標(biāo)題:go語(yǔ)言筆記書評(píng) go語(yǔ)言哪本書比較好
轉(zhuǎn)載源于:http://jinyejixie.com/article48/dohhphp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站設(shè)計(jì)公司、品牌網(wǎng)站制作、域名注冊(cè)、網(wǎng)站改版、網(wǎng)頁(yè)設(shè)計(jì)公司、企業(yè)建站
聲明:本網(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í)需注明來(lái)源: 創(chuàng)新互聯(lián)