成人午夜视频全免费观看高清-秋霞福利视频一区二区三区-国产精品久久久久电影小说-亚洲不卡区三一区三区一区

Go語(yǔ)言很好很強(qiáng)大,但我有幾個(gè)問(wèn)題想吐槽

2021-01-30    分類(lèi): 網(wǎng)站建設(shè)

Go 是一門(mén)非常不錯(cuò)的編程語(yǔ)言,并且逐漸取代 Python 成為很多人的選語(yǔ)言。但它也有一些缺點(diǎn)讓很多開(kāi)發(fā)者忍不住吐槽,比如它在函數(shù)式編程、通道 / 并行切片處理、內(nèi)存垃圾回收、錯(cuò)誤處理等方面都有一些問(wèn)題。本文作者將 Go 存在的“硬傷”設(shè)計(jì)記錄了下來(lái),與大家分享、討論。

Go 是一門(mén)非常不錯(cuò)的編程語(yǔ)言。然而,我在公司的 Slack 編程頻道中對(duì) Go 的抱怨卻越來(lái)越多(猜到我是做啥了的吧?),因此我認(rèn)為有必要把這些吐槽寫(xiě)下來(lái)并放在這里,這樣當(dāng)人們問(wèn)我抱怨什么時(shí),我給他們一個(gè)鏈接就行了。


先聲明一下,在過(guò)去的一年里,我大量地使用 Go 語(yǔ)言開(kāi)發(fā)命令行應(yīng)用程序、scc、lc 和 API。 其中既有供客戶(hù)端調(diào)用的大規(guī)模 API,也有即將在 https://searchcode.com/ 使用的 語(yǔ)法高亮顯示器。

我這些批評(píng)全部是針對(duì) Go 語(yǔ)言的。但是,我對(duì)使用過(guò)的每種語(yǔ)言都有不滿(mǎn)。 我非常贊同下面的話:

“世界上只有兩種語(yǔ)言:人們抱怨的語(yǔ)言和沒(méi)人使用的語(yǔ)言。” —— Bjarne Stroustrup

1、不支持函數(shù)式編程

我并不是一個(gè)函數(shù)式編程狂熱者。 說(shuō)到 Lisp 語(yǔ)言,我首先想到的是語(yǔ)言障礙。

這可能是 Go 語(yǔ)言大的痛點(diǎn)了。 與大部分人不同,我不希望 Go 支持泛型,因?yàn)樗鼤?huì)為多數(shù) Go 項(xiàng)目帶來(lái)不必要的復(fù)雜性。 我希望 Go 語(yǔ)言支持適用于內(nèi)置切片和 Map 的函數(shù)式方法。 切片和 Map 具有通用性,并且可以容納任何類(lèi)型,從這個(gè)意義上講,它們已經(jīng)非常神奇。在 Go 語(yǔ)言中只有利用接口才能實(shí)現(xiàn)類(lèi)似效果,但這樣一來(lái)將喪失安全性和速度。

例如,請(qǐng)考慮下面的問(wèn)題。

給定兩個(gè)字符串切片,找出二者都包含的字符串,并將其放入新的切片以備后用。

existsBoth := []string{}for _, first := range firstSlice {for _, second := range secondSlice {if first == second {existsBoth = append(existsBoth, proxy)break}}}

上面是一個(gè)用 Go 語(yǔ)言實(shí)現(xiàn)的簡(jiǎn)單方案。當(dāng)然還有其它方法,比如借助 Map 來(lái)減少運(yùn)行時(shí)間。這里我們假設(shè)內(nèi)存足夠用或者切片都不太大,同時(shí)假設(shè)優(yōu)化運(yùn)行時(shí)間帶來(lái)的復(fù)雜性遠(yuǎn)超收益,因此不值得優(yōu)化。作為對(duì)比,使用 Java 流和函數(shù)式編程把相同的邏輯重寫(xiě)如下:

var existsBoth = firstList.stream().filter(x -> secondList.contains(x)).collect(Collectors.toList());

上面的代碼隱藏了算法的復(fù)雜性,但是,你更容易理解它實(shí)際做的事情。

與 Go 代碼相比,Java 代碼的意圖一目了然。 真正靈活之處在于,添加更多的過(guò)濾條件易如反掌。 如果使用 Go 語(yǔ)言添加下面例子中的過(guò)濾條件,我們需要在嵌套的 for 循環(huán)中再添加兩個(gè) if 條件。

var existsBoth = firstList.stream().filter(x -> secondList.contains(x)).filter(x -> x.startsWith(needle)).filter(x -> x.length() >= 5).collect(Collectors.toList());

有些借助 go generate 命令的項(xiàng)目可以幫你實(shí)現(xiàn)上面的一些功能。但是,如果缺少良好的 IDE 支持,抽取循環(huán)中的語(yǔ)句作為單獨(dú)的方法是一件低效又麻煩的事情 。

2、通道 / 并行切片處理

Go 通道通常都很好用。 但它并不能提供無(wú)限的并發(fā)能力。它確實(shí)存在一些會(huì)導(dǎo)致永久阻塞的問(wèn)題,但這些問(wèn)題用競(jìng)爭(zhēng)檢測(cè)器能很容易地解決。對(duì)于數(shù)量不確定或不知何時(shí)結(jié)束的流式數(shù)據(jù),以及非 CPU 密集型的數(shù)據(jù)處理方法,Go 通道都是很好的選擇。

Go 通道不太適合并行處理大小已知的切片。


3、多線程編程、理論和實(shí)踐

幾乎在其它任何語(yǔ)言中,當(dāng)列表或切片很大時(shí),為了充分利用所有 CPU 內(nèi)核,通常都會(huì)使用并行流、并行 Linq、Rayon、多處理或其它語(yǔ)法來(lái)遍歷列表。遍歷后的返回值是一個(gè)包含已處理元素的列表。 如果元素足夠多,或者處理元素的函數(shù)足夠復(fù)雜,多核系統(tǒng)會(huì)更高效。

但是在 Go 語(yǔ)言中,實(shí)現(xiàn)高效處理所需要做的事情卻并不顯而易見(jiàn)。

一種可能的解決方案是為切片中的每個(gè)元素都創(chuàng)建一個(gè) Go 例程。 由于 Go 例程的開(kāi)銷(xiāo)很低,因此從某種程度上來(lái)說(shuō)這是一個(gè)有效的策略。

toProcess := []int{1,2,3,4,5,6,7,8,9}var wg sync.WaitGroupfor i, _ := range toProcess {wg.Add(1)go func(j int) {toProcess[j] = someSlowCalculation(toProcess[j])wg.Done()}(i)}wg.Wait()fmt.Println(toProcess)

上面的代碼會(huì)保持切片中元素的順序,但我們假設(shè)不必保持元素順序。

這段代碼的第一個(gè)問(wèn)題是增加了一個(gè) WaitGroup,并且必須要記得調(diào)用它的 Add 和 Done 方法。這增加了開(kāi)發(fā)人員的工作量。如果弄錯(cuò)了,這個(gè)程序不會(huì)產(chǎn)生正確的輸出,結(jié)果是要么輸出不確定,要么程序永不結(jié)束。此外,如果列表很長(zhǎng),你會(huì)為每個(gè)列表創(chuàng)建一個(gè) Go 例程。正如我之前所說(shuō),這不是問(wèn)題,因?yàn)?Go 能輕松搞定。問(wèn)題在于,每個(gè) Go 例程都會(huì)爭(zhēng)搶 CPU 時(shí)間片。因此,這不是執(zhí)行該任務(wù)的最有效方式。

你可能希望為每個(gè) CPU 內(nèi)核創(chuàng)建一個(gè) Go 例程,并讓這些例程選取列表并處理。創(chuàng)建 Go 例程的開(kāi)銷(xiāo)很小,但是在一個(gè)非常緊湊的循環(huán)中創(chuàng)建它們會(huì)使開(kāi)銷(xiāo)陡增。當(dāng)我開(kāi)發(fā) scc 時(shí)就遇到了這種情況,因此我采用了每個(gè) CPU 內(nèi)核對(duì)應(yīng)一個(gè) Go 例程的策略。在 Go 語(yǔ)言中,要這樣做的話,你首先要?jiǎng)?chuàng)建一個(gè)通道,然后遍歷切片中的元素,使函數(shù)從該通道讀取數(shù)據(jù),之后從另一個(gè)通道讀取。我們來(lái)看一下。

toProcess := []int{1,2,3,4,5,6,7,8,9}var input = make(chan int, len(toProcess))for i, _ := range toProcess {input <- i}close(input)var wg sync.WaitGroupfor i := 0; i < runtime.NumCPU(); i++ {wg.Add(1)go func(input chan int, output []int) {for j := range input {toProcess[j] = someSlowCalculation(toProcess[j])}wg.Done()}(input, toProcess)}wg.Wait()fmt.Println(toProcess)

上面的代碼創(chuàng)建了一個(gè)通道,然后遍歷切片,將索引值放入通道。 接下來(lái)我們?yōu)槊總€(gè) CPU 內(nèi)核創(chuàng)建一個(gè) Go 例程,操作系統(tǒng)會(huì)報(bào)告并處理相應(yīng)的輸入,然后等待,直到所有操作完成。這里有很多代碼需要理解。

然而,這種實(shí)現(xiàn)有待商榷。如果切片非常大,通道的緩沖區(qū)長(zhǎng)度和切片大小相同,你可能不希望創(chuàng)建一個(gè)有這么大緩沖區(qū)的通道。因此,你應(yīng)該創(chuàng)建另一個(gè) Go 例程來(lái)遍歷切片,并將切片中的值放入通道,完成后關(guān)閉通道。 但這樣一來(lái)代碼會(huì)變得冗長(zhǎng),因此我把它去掉了。我希望可以大概地闡明基本思路。

使用 Java 語(yǔ)言大致這樣實(shí)現(xiàn):

var firstList = List.of(1,2,3,4,5,6,7,8,9);firstList = firstList.parallelStream().map(this::someSlowCalculation).collect(Collectors.toList());

通道和流并不等價(jià)。 使用隊(duì)列去仿寫(xiě) Go 代碼的邏輯更好一些,因?yàn)樗鼈兏哂锌杀刃裕覀兊哪康牟皇沁M(jìn)行 1 對(duì) 1 的比較。 我們的目標(biāo)是充分利用所有的 CPU 內(nèi)核處理切片或列表。

如果 someSlowCalucation 方法調(diào)用了網(wǎng)絡(luò)或其它非 CPU 密集型任務(wù),這當(dāng)然不是問(wèn)題。 在這種情況下,通道和 Go 例程都會(huì)表現(xiàn)得很好。

這個(gè)問(wèn)題與問(wèn)題#1 有關(guān)。如果 Go 語(yǔ)言支持適用于切片 /Map 對(duì)象的函數(shù)式方法,那么就能實(shí)現(xiàn)這個(gè)功能。 但是,如果 Go 語(yǔ)言支持泛型,有人就可以把上面的功能封裝成像 Rust 的 Rayon 一樣的庫(kù),讓每個(gè)人都從中受益,這就很令人討厭了(我不希望 Go 支持泛型)。

順便說(shuō)一下,我認(rèn)為這個(gè)缺陷妨礙了 Go 語(yǔ)言在數(shù)據(jù)科學(xué)領(lǐng)域的成功,這也是為什么 Python 仍然是數(shù)據(jù)科學(xué)領(lǐng)域的王者。 Go 語(yǔ)言在數(shù)值操作方面缺乏表現(xiàn)力和能力,原因就是以上討論的這些。

4、垃圾回收器

Go 的垃圾回收器做得非常不錯(cuò)。我開(kāi)發(fā)的應(yīng)用程序通常都會(huì)因?yàn)樾掳姹镜母倪M(jìn)而變得更快。但是,它以低延遲為高優(yōu)先級(jí)。對(duì)于 API 和 UI 應(yīng)用來(lái)說(shuō),這個(gè)選擇完全可以接受。對(duì)于包含網(wǎng)絡(luò)調(diào)用的應(yīng)用,因?yàn)榫W(wǎng)絡(luò)調(diào)用往往會(huì)是瓶頸,所以它也沒(méi)問(wèn)題。

我發(fā)現(xiàn)的問(wèn)題是 Go 對(duì) UI 應(yīng)用來(lái)講一點(diǎn)也不好(我不知道它有任何良好的支持)。如果你想要盡可能高的吞吐量,那這個(gè)選擇會(huì)讓你很受傷。這是我開(kāi)發(fā) scc 時(shí)遇到的一個(gè)主要問(wèn)題。scc 是一個(gè) CPU 密集型的命令行工具。為了解決這個(gè)問(wèn)題,我不得不在代碼里添加邏輯關(guān)閉 GC,直到達(dá)到某個(gè)閾值。但是我又不能簡(jiǎn)單的禁用它,因?yàn)橛行┤蝿?wù)會(huì)很快耗盡內(nèi)存。

缺乏對(duì) GC 的控制時(shí)常令人沮喪。你得學(xué)會(huì)適應(yīng)它,但是,有時(shí)候如果能做到這樣該有多好:“嘿,這些代碼確實(shí)需要盡可能快地運(yùn)行,所以如果你能在高吞吐模式運(yùn)行一會(huì),那就太好了?!?/p>


我認(rèn)為這種情況在 Go 1.12 版本中有所改善,因?yàn)?GC 得到了進(jìn)一步的改進(jìn)。但僅僅是關(guān)閉和打開(kāi) GC 還不夠,我期望更多的控制。 如果有時(shí)間我會(huì)再進(jìn)行研究。

5、錯(cuò)誤處理

我并不是唯一一個(gè)抱怨這個(gè)問(wèn)題的人,但我不吐不快。

value, err := someFunc()if err != nil {// Do something here}err = someOtherFunc(value)if err != nil {// Do something here}

上面的代碼很乏味。 Go 甚至不會(huì)像有些人建議的那樣強(qiáng)制你處理錯(cuò)誤。 你可以使用“_”顯式忽略它(這是否算作對(duì)它進(jìn)行了處理呢?),你還可以完全忽略它。比如上面的代碼可以重寫(xiě)為:

value, _ := someFunc()someOtherFunc(value)

很顯然,我顯式忽略了 someFunc 方法的返回。someOtherFunc(value)方法也可能返回錯(cuò)誤值,但我完全忽略了它。 這里的錯(cuò)誤都沒(méi)有得到處理。

說(shuō)實(shí)話,我不知道如何解決這個(gè)問(wèn)題。 我喜歡 Rust 中的“?” 運(yùn)算符,它可以幫助避免這種情況。V-Lang https://vlang.io/ 看起來(lái)也可能有一些有趣的解決方案。

另一個(gè)辦法是使用可選類(lèi)型(Optional types)并去掉 nil,但這不會(huì)發(fā)生在 Go 語(yǔ)言里,即使是 Go 2.0 版本,因?yàn)樗鼤?huì)破壞向后兼容性。

結(jié) 語(yǔ)

Go 仍然是一種非常不錯(cuò)的語(yǔ)言。如果你讓我寫(xiě)一個(gè) API,或者完成某個(gè)需要大量磁盤(pán) / 網(wǎng)絡(luò)調(diào)用的任務(wù),它依然是我的選?,F(xiàn)在我會(huì)用 Go 而非 Python 去完成很多一次性任務(wù),數(shù)據(jù)合并任務(wù)是例外,因?yàn)楹瘮?shù)式編程的缺失使執(zhí)行效率難以達(dá)到要求。

與 Java 不同,Go 語(yǔ)言盡量遵循“最小驚喜“原則。比如可以這樣比較字兩個(gè)符串是否相等:stringA == stringB。但如果你這樣比較兩個(gè)切片,那么會(huì)產(chǎn)生編譯錯(cuò)誤。這些都是很好的特性。

的確,二進(jìn)制文件還可以變的更?。ㄒ恍?編譯標(biāo)志和 upx 可以解決這個(gè)問(wèn)題),我希望它在某些方面變得更快,GOPATH 雖然不是很好,但也沒(méi)有人們想得那么糟糕,默認(rèn)的單元測(cè)試框架缺少很多功能,模擬(mocking)有點(diǎn)讓人痛苦......

它仍然是我使用過(guò)的效率較高的語(yǔ)言之一。我會(huì)繼續(xù)使用它,雖然我希望 https://vlang.io/ 能最終發(fā)布,并解決我的很多抱怨。V 語(yǔ)言或 Go 2.0,Nim 或 Rust。現(xiàn)在有很多很酷的新語(yǔ)言可以使用,我們開(kāi)發(fā)人員真的要被寵壞了。

網(wǎng)頁(yè)標(biāo)題:Go語(yǔ)言很好很強(qiáng)大,但我有幾個(gè)問(wèn)題想吐槽
路徑分享:http://jinyejixie.com/news/98207.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供響應(yīng)式網(wǎng)站、軟件開(kāi)發(fā)面包屑導(dǎo)航、網(wǎng)站改版外貿(mào)網(wǎng)站建設(shè)、微信小程序

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(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)

h5響應(yīng)式網(wǎng)站建設(shè)
房山区| 抚松县| 玉溪市| 夏津县| 德州市| 牙克石市| 根河市| 周至县| 武宣县| 太仓市| 龙川县| 东乡族自治县| 泰来县| 桃江县| 类乌齐县| 江西省| 靖西县| 宾阳县| 年辖:市辖区| 竹山县| 西充县| 会同县| 焦作市| 仙居县| 东乌珠穆沁旗| 兴业县| 通州市| 财经| 共和县| 陆良县| 闵行区| 吴忠市| 怀化市| 德惠市| 文山县| 鄂托克前旗| 宝应县| 灵川县| 祁连县| 柏乡县| 寿光市|