參考:
網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)!專(zhuān)注于網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)站建設(shè)、微信開(kāi)發(fā)、成都微信小程序、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項(xiàng)目。為回饋新老客戶(hù)創(chuàng)新互聯(lián)還提供了海棠免費(fèi)建站歡迎大家使用!
Goroutine并發(fā)調(diào)度模型深度解析手?jǐn)]一個(gè)協(xié)程池
Golang 的 goroutine 是如何實(shí)現(xiàn)的?
Golang - 調(diào)度剖析【第二部分】
OS線程初始棧為2MB。Go語(yǔ)言中,每個(gè)goroutine采用動(dòng)態(tài)擴(kuò)容方式,初始2KB,按需增長(zhǎng),最大1G。此外GC會(huì)收縮??臻g。
BTW,增長(zhǎng)擴(kuò)容都是有代價(jià)的,需要copy數(shù)據(jù)到新的stack,所以初始2KB可能有些性能問(wèn)題。
更多關(guān)于stack的內(nèi)容,可以參見(jiàn)大佬的文章。 聊一聊goroutine stack
用戶(hù)線程的調(diào)度以及生命周期管理都是用戶(hù)層面,Go語(yǔ)言自己實(shí)現(xiàn)的,不借助OS系統(tǒng)調(diào)用,減少系統(tǒng)資源消耗。
Go語(yǔ)言采用兩級(jí)線程模型,即用戶(hù)線程與內(nèi)核線程KSE(kernel scheduling entity)是M:N的。最終goroutine還是會(huì)交給OS線程執(zhí)行,但是需要一個(gè)中介,提供上下文。這就是G-M-P模型
Go調(diào)度器有兩個(gè)不同的運(yùn)行隊(duì)列:
go1.10\src\runtime\runtime2.go
Go調(diào)度器根據(jù)事件進(jìn)行上下文切換。
調(diào)度的目的就是防止M堵塞,空閑,系統(tǒng)進(jìn)程切換。
詳見(jiàn) Golang - 調(diào)度剖析【第二部分】
Linux可以通過(guò)epoll實(shí)現(xiàn)網(wǎng)絡(luò)調(diào)用,統(tǒng)稱(chēng)網(wǎng)絡(luò)輪詢(xún)器N(Net Poller)。
文件IO操作
上面都是防止M堵塞,任務(wù)竊取是防止M空閑
每個(gè)M都有一個(gè)特殊的G,g0。用于執(zhí)行調(diào)度,gc,棧管理等任務(wù),所以g0的棧稱(chēng)為調(diào)度棧。g0的棧不會(huì)自動(dòng)增長(zhǎng),不會(huì)被gc,來(lái)自os線程的棧。
go1.10\src\runtime\proc.go
G沒(méi)辦法自己運(yùn)行,必須通過(guò)M運(yùn)行
M通過(guò)通過(guò)調(diào)度,執(zhí)行G
從M掛載P的runq中找到G,執(zhí)行G
應(yīng)puppet大拿劉宇的邀請(qǐng),我去西山居運(yùn)維團(tuán)隊(duì)做了一個(gè)簡(jiǎn)短分享,談?wù)劄槭裁次乙獙⑽覀兊捻?xiàng)目從python轉(zhuǎn)向go。
坦白的講,在一幫python用戶(hù)面前講為什么放棄python轉(zhuǎn)而用go其實(shí)是一件壓力蠻大的事情,語(yǔ)言之爭(zhēng)就跟vim和emacs之爭(zhēng)一樣,是一個(gè)永恒的無(wú)解話題,稍微不注意就可能導(dǎo)致粉絲強(qiáng)烈地反擊。所以我只會(huì)從我們項(xiàng)目實(shí)際情況出發(fā),來(lái)講講為什么我最終選擇了go。
為什么放棄python
首先,我其實(shí)得說(shuō)說(shuō)為什么我們會(huì)選擇python。在我加入企業(yè)快盤(pán)團(tuán)隊(duì)之前,整個(gè)項(xiàng)目包括更早的金山快盤(pán)都是采用python進(jìn)行開(kāi)發(fā)的。至于為什么這么選擇,當(dāng)時(shí)的架構(gòu)師蔥頭告訴我,主要是因?yàn)閜ython上手簡(jiǎn)單,開(kāi)發(fā)迅速。對(duì)于團(tuán)隊(duì)里面大部分完全沒(méi)服務(wù)端開(kāi)發(fā)經(jīng)驗(yàn)的同學(xué)來(lái)說(shuō),python真的是一個(gè)很好的選擇。
python的簡(jiǎn)單高效,我是深有體會(huì)的。當(dāng)時(shí)私有云項(xiàng)目也就幾個(gè)程序員,但是我們要服務(wù)多家大型企業(yè),進(jìn)行定制化的開(kāi)發(fā),多虧了python,我們才能快速出活。后來(lái)企業(yè)快盤(pán)掛掉之后,我們啟動(dòng)輕辦公項(xiàng)目,自然也使用python進(jìn)行了原始版本的構(gòu)建。
python雖然很強(qiáng)大,但我們?cè)谑褂玫臅r(shí)候也碰到了一些問(wèn)題,主要由如下幾個(gè)方面:
動(dòng)態(tài)語(yǔ)言
python是一門(mén)動(dòng)態(tài)強(qiáng)類(lèi)型語(yǔ)言。但是,仍然可能出現(xiàn)int + string這樣的運(yùn)行時(shí)錯(cuò)誤,因?yàn)閷?duì)于一個(gè)變量,在寫(xiě)代碼的時(shí)候,我們有時(shí)候很容易就忘記這個(gè)變量到底是啥類(lèi)型的了。
在python里面,可以允許同名函數(shù)的出現(xiàn),后一個(gè)函數(shù)會(huì)覆蓋前一個(gè)函數(shù),有一次我們系統(tǒng)一個(gè)很?chē)?yán)重的錯(cuò)誤就是因?yàn)檫@個(gè)導(dǎo)致的。
上面說(shuō)到的這些,靜態(tài)語(yǔ)言在編譯的時(shí)候就能幫我們檢測(cè)出來(lái),而不需要等到運(yùn)行時(shí)出問(wèn)題才知道。雖然我們有很完善的測(cè)試用例,但總有case遺漏的情況。所以每次出現(xiàn)運(yùn)行時(shí)錯(cuò)誤,我心里都想著如果能在編譯的時(shí)候就發(fā)現(xiàn)該多好。
性能
其實(shí)這個(gè)一直是很多人吐槽python的地方,但python有它適合干的事情,硬是要用python進(jìn)行一些高性能模塊的開(kāi)發(fā),那也有點(diǎn)難為它了。
python的GIL導(dǎo)致無(wú)法真正的多線程,大家可能會(huì)說(shuō)我用多進(jìn)程不就完了。但如果一些計(jì)算需要涉及到多進(jìn)程交互,進(jìn)程之間的通訊開(kāi)銷(xiāo)也是不得不考慮的。
無(wú)狀態(tài)的分布式處理使用多進(jìn)程很方便,譬如處理http請(qǐng)求,我們就是在nginx后面掛載了200多個(gè)django server來(lái)處理http的,但這么多個(gè)進(jìn)程自然導(dǎo)致整體機(jī)器負(fù)載偏高。
但即使我們使用了多個(gè)django進(jìn)程來(lái)處理http請(qǐng)求,對(duì)于一些超大量請(qǐng)求,python仍然處理不過(guò)來(lái)。所以我們使用openresty,將高頻次的http請(qǐng)求使用lua來(lái)實(shí)現(xiàn)??蛇@樣又導(dǎo)致使用兩種開(kāi)發(fā)語(yǔ)言,而且一些邏輯還得寫(xiě)兩份不同的代碼。
同步網(wǎng)絡(luò)模型
django的網(wǎng)絡(luò)是同步阻塞的,也就是說(shuō),如果我們需要訪問(wèn)外部的一個(gè)服務(wù),在等待結(jié)果返回這段時(shí)間,django不能處理任何其他的邏輯(當(dāng)然,多線程的除外)。如果訪問(wèn)外部服務(wù)需要很長(zhǎng)時(shí)間,那就意味著我們的整個(gè)服務(wù)幾乎在很長(zhǎng)一段時(shí)間完全不可用。
為了解決這個(gè)問(wèn)題,我們只能不斷的多開(kāi)django進(jìn)程,同時(shí)需要保證所有服務(wù)都能快速的處理響應(yīng),但想想這其實(shí)是一件很不靠譜的事情。
異步網(wǎng)絡(luò)模型
tornado的網(wǎng)絡(luò)模型是異步的,這意味著它不會(huì)出現(xiàn)django那樣因?yàn)橥獠糠?wù)不可用導(dǎo)致這個(gè)服務(wù)無(wú)法響應(yīng)的問(wèn)題。話說(shuō),比起django,我可是非常喜歡tornado的,小巧簡(jiǎn)單,以前還寫(xiě)過(guò)幾篇深入剖析tornado的文章了。
雖然tornado是異步的,但是python的mysql庫(kù)都不支持異步,這也就意味著如果我們?cè)趖ornado里面訪問(wèn)數(shù)據(jù)庫(kù),我們?nèi)匀豢赡苊媾R因?yàn)閿?shù)據(jù)庫(kù)問(wèn)題造成的整個(gè)服務(wù)不可用。
其實(shí)異步模型最大的問(wèn)題在于代碼邏輯的割裂,因?yàn)槭鞘录|發(fā)的,所以我們都是通過(guò)callback進(jìn)行相關(guān)處理,于是代碼里面就經(jīng)常出現(xiàn)干一件事情,傳一個(gè)callback,然后callback里面又傳callback的情況,這樣的結(jié)果就是整個(gè)代碼邏輯非?;靵y。
python沒(méi)有原生的協(xié)程支持,雖然可以通過(guò)gevent,greenlet這種的上patch方式來(lái)支持協(xié)程,但畢竟更改了python源碼。另外,python的yield也可以進(jìn)行簡(jiǎn)單的協(xié)程模擬,但畢竟不能跨堆棧,局限性很大,不知道3.x的版本有沒(méi)有改進(jìn)。
開(kāi)發(fā)運(yùn)維部署
當(dāng)我第一次使用python開(kāi)發(fā)項(xiàng)目,我是沒(méi)成功安裝上項(xiàng)目需要的包的,光安裝成功mysql庫(kù)就弄了很久。后來(lái),是一位同事將他整個(gè)python目錄打包給我用,我才能正常的將項(xiàng)目跑起來(lái)。話說(shuō),現(xiàn)在有了docker,是多么讓人幸福的一件事情。
而部署python服務(wù)的時(shí)候,我們需要在服務(wù)器上面安裝一堆的包,光是這一點(diǎn)就讓人很麻煩,雖然可以通過(guò)puppet,salt這些自動(dòng)化工具解決部署問(wèn)題,但相比而言,靜態(tài)編譯語(yǔ)言只用扔一個(gè)二進(jìn)制文件,可就方便太多了。
代碼失控
python非常靈活簡(jiǎn)單,寫(xiě)c幾十行代碼才能搞定的功能,python一行代碼沒(méi)準(zhǔn)就能解決。但是太簡(jiǎn)單,反而導(dǎo)致很多同學(xué)無(wú)法對(duì)代碼進(jìn)行深層次的思考,對(duì)整個(gè)架構(gòu)進(jìn)行細(xì)致的考量。來(lái)了一個(gè)需求,啪啪啪,鍵盤(pán)敲完開(kāi)速實(shí)現(xiàn),結(jié)果就是代碼越來(lái)越混亂,最終導(dǎo)致了整個(gè)項(xiàng)目代碼失控。
雖然這也有我們自身的原因,譬如沒(méi)好的代碼review機(jī)制,沒(méi)有好的項(xiàng)目規(guī)范,但個(gè)人感覺(jué),如果一個(gè)程序員沒(méi)經(jīng)過(guò)良好的編碼訓(xùn)練,用python很容易就寫(xiě)出爛的代碼,因?yàn)樘杂闪恕?/p>
當(dāng)然,我這里并不是說(shuō)用python無(wú)法進(jìn)行大型項(xiàng)目的開(kāi)發(fā),豆瓣,dropbox都是很好的例子,只是在我們項(xiàng)目中,我們的python代碼失控了。
上面提到的都是我們?cè)趯?shí)際項(xiàng)目中使用python遇到的問(wèn)題,雖然最終都解決了,但是讓我愈發(fā)的覺(jué)得,隨著項(xiàng)目復(fù)雜度的增大,流量性能壓力的增大,python并不是一個(gè)很好的選擇。
為什么選擇go
說(shuō)完了python,現(xiàn)在來(lái)說(shuō)說(shuō)為什么我們選擇go。其實(shí)除了python,我們也有其他的選擇,java,php,lua(openresty),但最終我們選擇了go。
雖然java和php都是最好的編程語(yǔ)言(大家都這么爭(zhēng)的),但我更傾向一門(mén)更簡(jiǎn)單的語(yǔ)言。而openresty,雖然性能強(qiáng)悍,但lua仍然是動(dòng)態(tài)語(yǔ)言,也會(huì)碰到前面說(shuō)的動(dòng)態(tài)語(yǔ)言一些問(wèn)題。最后,前金山許式偉用的go,前快盤(pán)架構(gòu)師蔥頭也用的go,所以我們很自然地選擇了go。
go并不是完美,一堆值得我們吐槽的地方。
error,好吧,如果有語(yǔ)言潔癖的同學(xué)可能真的受不了go的語(yǔ)法,尤其是約定的最后一個(gè)返回值是error。項(xiàng)目里面經(jīng)常會(huì)充斥這樣的代碼:
if _, err := w.Write(data1); err != nil {
returun err
}
if _, err := w.Write(data2); err != nil {
returun err
}
難怪有個(gè)梗是對(duì)于一個(gè)需求,java的程序員在寫(xiě)配置的時(shí)候,go程序員已經(jīng)寫(xiě)了大部分代碼,但是當(dāng)java的程序員寫(xiě)完的時(shí)候,go程序員還在寫(xiě)err != nil。
這方面,errors-are-values倒是推薦了一個(gè)不錯(cuò)的解決方案。
包管理,go的包管理太弱了,只有一個(gè)go get,也就是如果不小心更新了一個(gè)外部庫(kù),很有可能就導(dǎo)致現(xiàn)有的代碼編譯不過(guò)了。雖然已經(jīng)有很多開(kāi)源方案,譬如godep以及現(xiàn)在才出來(lái)的gb等,但畢竟不是官方的。貌似google也是通過(guò)vendor機(jī)制來(lái)管理第三方庫(kù)的。希望go 1.5或者之后的版本能好好處理下這個(gè)問(wèn)題。
GC,java的GC發(fā)展20年了,go才這么點(diǎn)時(shí)間,gc鐵定不完善。所以我們?nèi)匀徊荒茈S心所欲的寫(xiě)代碼,不然在大請(qǐng)求量下面gc可能會(huì)卡頓整個(gè)服務(wù)。所以有時(shí)候,該用對(duì)象池,內(nèi)存池的一定要用,雖然代碼丑了點(diǎn),但好歹性能上去了。
泛型,雖然go有inteface,但泛型的缺失會(huì)讓我們?cè)趯?shí)現(xiàn)一個(gè)功能的時(shí)候?qū)懘罅康闹貜?fù)代碼,譬如int32和int64類(lèi)型的sort,我們得為分別寫(xiě)兩套代碼,好冗余。go 1.4之后有了go generate的支持,但這種的仍然需要自己根據(jù)go的AST庫(kù)來(lái)手動(dòng)寫(xiě)相關(guān)的parser,難度也挺大的。雖然也有很多開(kāi)源的generate實(shí)現(xiàn),但畢竟不是官方的。
當(dāng)然還有很多值得吐槽的地方,就不一一列舉了,但是go仍舊有它的優(yōu)勢(shì)。
靜態(tài)語(yǔ)言,強(qiáng)類(lèi)型。靜態(tài)編譯能幫我們檢查出來(lái)大量的錯(cuò)誤,go的強(qiáng)類(lèi)型甚至變態(tài)到不支持隱式的類(lèi)型轉(zhuǎn)換。雖然寫(xiě)代碼感覺(jué)很別扭,但減少了犯錯(cuò)的可能。
gofmt,應(yīng)該這是我知道的第一個(gè)官方提供統(tǒng)一格式化代碼工具的語(yǔ)言了。有了gofmt,大家的代碼長(zhǎng)一個(gè)樣了,也就沒(méi)有花括號(hào)到底放到結(jié)尾還是新開(kāi)一行這種蛋疼的代碼風(fēng)格討論了。因?yàn)榇蠹业拇a風(fēng)格一樣,所以看go的代碼很容易。
天生的并行支持,因?yàn)間oroutine以及channel,用go寫(xiě)分布式應(yīng)用,寫(xiě)并發(fā)程序異常的容易。沒(méi)有了蛋疼的callback導(dǎo)致的代碼邏輯割裂,代碼邏輯都是順序的。
性能,go的性能可能趕不上c,c++以及openresty,但真的也挺強(qiáng)悍的。在我們的項(xiàng)目中,現(xiàn)在單機(jī)就部署了一個(gè)go的進(jìn)程,就完全能夠勝任以前200個(gè)python進(jìn)程干的事情,而且CPU和MEM占用更低。
運(yùn)維部署,直接編譯成二進(jìn)制,扔到服務(wù)器上面就成,比python需要安裝一堆的環(huán)境那是簡(jiǎn)單的太多了。當(dāng)然,如果有cgo,我們也需要將對(duì)應(yīng)的動(dòng)態(tài)庫(kù)給扔過(guò)去。
開(kāi)發(fā)效率,雖然go是靜態(tài)語(yǔ)言,但我個(gè)人感覺(jué)開(kāi)發(fā)效率真的挺高,直覺(jué)上面跟python不相上下。對(duì)于我個(gè)人來(lái)說(shuō),最好的例子就是我用go快速開(kāi)發(fā)了非常多的開(kāi)源組件,譬如ledisdb,go-mysql等,而這些最開(kāi)始的版本都是在很短的時(shí)間里面完成的。對(duì)于我們項(xiàng)目來(lái)說(shuō),我們也是用go在一個(gè)月就重構(gòu)完成了第一個(gè)版本,并發(fā)布。
實(shí)際項(xiàng)目中一些Go Tips
到現(xiàn)在為止,我們幾乎所有的服務(wù)端項(xiàng)目都已經(jīng)轉(zhuǎn)向go,當(dāng)然在使用的時(shí)候也遇到了一些問(wèn)題,列出來(lái)算是經(jīng)驗(yàn)分享吧。
godep,我們使用godep進(jìn)行第三方庫(kù)管理,但是godep我碰到的最大的坑就是build tag問(wèn)題,如果一個(gè)文件有build tag,godep很有可能就會(huì)忽略這個(gè)文件。
IO deadline,如果能自己在應(yīng)用層處理的都自己處理,go的deadline內(nèi)部是timer來(lái)控制,但timer內(nèi)部采用一個(gè)array來(lái)實(shí)現(xiàn)的heap,全局共用一個(gè)鎖,如果大并發(fā)量,并且timer數(shù)量過(guò)多,timeout變動(dòng)太頻繁,很容易就引起性能問(wèn)題。
GC,這個(gè)前面也說(shuō)了,多用內(nèi)存池,對(duì)象池,另外,我還發(fā)現(xiàn),如果對(duì)象的生命周期跟goroutine一致,對(duì)性能的提升也不錯(cuò),也在go的group問(wèn)過(guò)相關(guān)問(wèn)題,大家猜測(cè)可能是因?yàn)橐恍?duì)象其實(shí)是在goroutine的8k棧上面分配的,所以一起回收沒(méi)有額外GC了。
Go gob,如果要做RPC服務(wù),gob并不是一個(gè)很好的選擇,首先就跟python的pickle不通用,然后為了做不同系統(tǒng)的數(shù)據(jù)傳入,任何包都必須帶上類(lèi)型的詳細(xì)信息,size太大。go里面現(xiàn)在還沒(méi)一套官方的RPC方案,gRPC貌似有上位的可能。
【安卓 Android 設(shè)備】
app 的語(yǔ)言跟隨手機(jī)系統(tǒng)的語(yǔ)言設(shè)置,若您需要修改 app 語(yǔ)言,直接調(diào)整手機(jī)系統(tǒng)的語(yǔ)言設(shè)置即可;
【蘋(píng)果 iOS】
進(jìn)入 app 主頁(yè),點(diǎn)擊右下角【我】- 右上角【設(shè)置】-【設(shè)置語(yǔ)言】。
轉(zhuǎn)載請(qǐng)參見(jiàn)文章末尾處的要求。【感謝張佳偉(@ghosert)的熱心翻譯。如果其他朋友也有不錯(cuò)的原創(chuàng)或譯文,可以嘗試推薦給伯樂(lè)在線?!窟@是一篇(長(zhǎng))博文, 介紹了我們?cè)?Repustate 遷移大量 Python/Cython 代碼到 Go 語(yǔ)言的經(jīng)驗(yàn)。如果你想了解整個(gè)故事,背景和所有的事情,請(qǐng)繼續(xù)往下讀。如果你只是想了解 Python 開(kāi)發(fā)者在一頭扎進(jìn) Go 語(yǔ)言前需要了解什么,請(qǐng)點(diǎn)擊一下鏈接:從Python遷移到Go的建議(Tips Tricks) 背景在Repustate,我們完成過(guò)的最棒的技術(shù)成就之一是實(shí)現(xiàn)了阿拉伯語(yǔ)的情感分析。阿拉伯語(yǔ)是一塊難啃的硬骨頭,因?yàn)樗脑~形變化相當(dāng)復(fù)雜。比起譬如英語(yǔ),阿拉伯語(yǔ)的分詞(將一個(gè)句子切分呈幾個(gè)獨(dú)立的單詞)也更困難,因?yàn)榘⒗Z(yǔ)的單詞本身還可能會(huì)包含空白字符(例如:“阿列夫”在一個(gè)單詞里的位置)。這也談不上是泄密,Repustate 使用支持向量機(jī)(SVM)來(lái)獲取一個(gè)句子背后最有可能的含義,并在其中加上情感元素。 總體上來(lái)說(shuō),我們使用了 22 種模型(22 個(gè) SVM) 并且在一篇文檔中,每一個(gè)單詞我們都會(huì)加以分析。因此如果你有一篇 500 字的文檔,那么基于 SVM,會(huì)進(jìn)行十萬(wàn)次的比較。 PythonRepustate 幾乎完全就是一個(gè) Python 商店。我們使用 Django 來(lái)實(shí)現(xiàn) API 和網(wǎng)站。因此(目前)為了保持代碼一致,同時(shí)使用 Python 來(lái)實(shí)現(xiàn)阿拉伯語(yǔ)情感引擎是合情合理的。只是做原型和實(shí)現(xiàn)的話,Python 是很好的選擇。它的表達(dá)能力很強(qiáng)悍,第三方類(lèi)庫(kù)等等也很好。如果你就是為了Web服務(wù),Python 很完美。但是當(dāng)你進(jìn)行低級(jí)別的計(jì)算,大量依賴(lài)于哈希表(Python 里的字典類(lèi)型)做比較的時(shí)候,一切都變慢了。我們每秒能處理大約兩到三個(gè)阿拉伯文檔,但是這太慢了。比較下來(lái),我們的英語(yǔ)情感引擎每秒能處理大約五百份文檔。 瓶頸因此我們開(kāi)啟了 Python 分析器,開(kāi)始調(diào)查是什么地方用了那么長(zhǎng)時(shí)間。還記得我前面說(shuō)過(guò)我們有 22 個(gè) SVM 并且每個(gè)單詞都需要經(jīng)過(guò)處理嗎?好吧,這些都是線性處理的,非并行處理。所以我們的第一反應(yīng)是把線性處理改成 map/reduce 那樣的操作。簡(jiǎn)單來(lái)說(shuō):Python 不太適合用作 map/reduce。當(dāng)你需要并發(fā)的時(shí)候,Python 算上好用。在 2013 Python 大會(huì)上(譯者:PyCon 2013),Guido 談到了 Tulip,他的這個(gè)新項(xiàng)目正在彌補(bǔ) Python 這方面的不足,不過(guò)得過(guò)段一段時(shí)間才能推出,但是如果已經(jīng)有了更好用的東西,我們?yōu)槭裁催€要等呢? 選Go 語(yǔ)言,還是回家算了?我在Mozilla的朋友告訴我,Mozilla 內(nèi)部正在將他們大量的基礎(chǔ)日志架構(gòu)切換到 Go 語(yǔ)言上,部分原因是因?yàn)閺?qiáng)大的 [goroutines]。Go 語(yǔ)言是 Google 的人設(shè)計(jì)的,并且在設(shè)計(jì)之初就把支持并發(fā)作為第一要?jiǎng)?wù),而不是像 Python 的各種解決方案那樣是事后才加上去的。因此我們開(kāi)始著手把 Python 換成 Go 語(yǔ)言。雖然Go 代碼還不算正式上線的產(chǎn)品,但是結(jié)果非常令人鼓舞。我們現(xiàn)在能做到每秒處理一千份文檔,使用更少的內(nèi)存,還不用調(diào)試你在 Python 里遇到:丑陋的多進(jìn)程/gevent/“為什么 Control-C 殺不了進(jìn)程”這些問(wèn)題。 為什么我們喜歡 Go 語(yǔ)言任何人,對(duì)編程語(yǔ)言是如何工作(解釋型 vs 編譯型, 動(dòng)態(tài)語(yǔ)言 vs 靜態(tài)語(yǔ)言)有一點(diǎn)理解的話,會(huì)說(shuō),“切,當(dāng)然 Go 語(yǔ)言會(huì)更快”。是的,我們也可以用 Java 把所有的東西重寫(xiě)一遍,也能看到類(lèi)似更快的改善,但那不是 Go 語(yǔ)言勝出的原因。你用 Go 寫(xiě)的代碼好像就是對(duì)的。我搞不清楚到底是怎么回事,但是一旦代碼被編譯了(編譯速度很快),你就會(huì)覺(jué)得這代碼能工作(不只是跑起來(lái)不會(huì)錯(cuò),而且甚至邏輯上也是對(duì)的)。我知道,這聽(tīng)上去不太靠譜,但是確實(shí)如此。這和 Python 在冗余(或非冗余)方面非常類(lèi)似,它把函數(shù)作為第一目標(biāo),因此函數(shù)編程會(huì)很容易想明白。而且當(dāng)然,go 線程和通道讓你的生活更容易,你可以得到靜態(tài)類(lèi)型帶來(lái)的性能大提升,還能更精細(xì)的控制內(nèi)存分配,而你卻不必為此在語(yǔ)言表達(dá)力上付出太多的代價(jià)。 希望能早點(diǎn)知道的事情(Tips Tricks)除去所有這些贊美之詞以后,有時(shí)你真的需要在處理 Go 代碼的時(shí)候,相對(duì)于 Python,改變一下思維方式。因此這是我在遷移代碼時(shí)記錄的筆記清單 —— 只是在我把 Python 代碼轉(zhuǎn)換到 Go 時(shí)從我腦子里隨機(jī)冒出來(lái)的點(diǎn)子:沒(méi)有內(nèi)建的集合類(lèi)型(必須使用map,并檢查是否存在)因?yàn)闆](méi)有集合,必須自己寫(xiě)交集,并集之類(lèi)的方法沒(méi)有tuples 類(lèi)型,必須寫(xiě)你自己的結(jié)構(gòu),或者使用 slices (即數(shù)組)沒(méi)有類(lèi)似 \__getattr__() 的方法,你必須總是檢查存在性,而不是設(shè)置默認(rèn)值,例如,在 Python 里,你可以這樣寫(xiě) value = dict.get(“a_key”, “default_value”)必須總是檢查錯(cuò)誤(或者顯式的忽略錯(cuò)誤)不能有變量/包沒(méi)被使用,因此簡(jiǎn)單的測(cè)試也需要有時(shí)注掉一些代碼在[] byte 和 string 之間轉(zhuǎn)換。 regexp 使用 [] byte (不可變)。這是對(duì)的,但是老把一些變量轉(zhuǎn)換來(lái)轉(zhuǎn)換去很煩人Python 更寬松。你可以使用超出范圍的索引在字符串里取一個(gè)片段,而且不會(huì)出錯(cuò)。你還可以用負(fù)數(shù)取出片段,但是 Go 不行你不能混合數(shù)據(jù)結(jié)構(gòu)類(lèi)型。也許這樣也不太干凈,但是有時(shí)在 Python 里,我會(huì)使用值是混合了字符串和列表的字典。但是 Go 不行,你不得不清理干凈你的數(shù)據(jù)結(jié)構(gòu)或者使用自定義的結(jié)構(gòu)不能解包一個(gè) tuple 或者 list 到幾個(gè)不同的變量(例如:x, y, z = [1, 2, 3])駝峰式命名風(fēng)格(如果你沒(méi)有首字大寫(xiě)方法名/結(jié)構(gòu)名,他們不會(huì)被暴露給其它的包)。我更喜歡 Python 的小寫(xiě)字母加下劃線命名風(fēng)格。必須顯式檢查是否有錯(cuò)誤 != nil, 不像在 Python 里,許多類(lèi)型可以像 bool 那樣檢查 (0, “”, None 都可以被解釋成 “非” 集合)文檔在一些模塊上太散亂了,例如(crypto/md5),但是 IRC 上的 go-nuts 很好用,提供了巨大的幫助。從數(shù)字到字符串的轉(zhuǎn)換(int64 - string) 和 []byte - string (只要使用 string([]byte))不太一樣。需要使用 strconv。閱讀Go 代碼比起 Python 那樣寫(xiě)起來(lái)如偽代碼的語(yǔ)言更像一門(mén)編程語(yǔ)言, Go 有更多的非字母數(shù)字字符,并且使用 || 和 , 而不是 “or”和“and”寫(xiě)一個(gè)文件的話,有 File.Write([]byte) 和 File.WriteString(string), 這點(diǎn)和 Python 開(kāi)發(fā)者的 Python 之道:“解決問(wèn)題就一種方法 ”相違背。修改字符串很困難,必須經(jīng)常重排 fmt.Sprintf沒(méi)有構(gòu)造函數(shù),因此慣用法是創(chuàng)建 NewType() 方法來(lái)返回你要的結(jié)構(gòu)Else (或者 else if)必須正確格式化,else 得和 if 配對(duì)的大括號(hào)在同一行。奇怪。賦值運(yùn)算符取決于在函數(shù)內(nèi)還是函數(shù)外,例如,= 和 :=如果我只想要“鍵”或者只想要 “值”,譬如: dict.keys() 或者 dict.values(),或者一個(gè) tuples 的列表,例如:dict.items(),在 Go 語(yǔ)言里沒(méi)有等價(jià)的東西,你只能自己枚舉 map 來(lái)構(gòu)造你的列表類(lèi)型我有時(shí)使用一種習(xí)慣用法:構(gòu)造一個(gè)值是函數(shù)的字典類(lèi)型,我想通過(guò)給定的鍵值調(diào)用這些函數(shù),你在 Go 里可以做到,但是所有的函數(shù)必須接受,返回相同的東西,例如:相同的方法簽名如果你使用 JSON 并且 你的 JSON 是一個(gè)復(fù)合類(lèi)型,恭喜你。 你必須構(gòu)造自定義的結(jié)構(gòu)匹配 JSON 塊里的格式,然后把原始 JSON 解析到你自定義結(jié)構(gòu)的實(shí)例中去。比起 Python 世界里 object = json.loads(json_blob) 要做更多的工作 是不是值得?值得,一百萬(wàn)倍的值得。速度的提升太多了,以致很難舍棄。同時(shí),我認(rèn)為, Go 是目前趨勢(shì)所在,因此在招新員工的時(shí)候,我認(rèn)為把 Go 當(dāng)作 Repustate 技術(shù)積累的重要一環(huán)會(huì)很有幫助。]