自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Go 通道是糟糕的,你應(yīng)該也覺(jué)得很糟糕

開發(fā) 后端
Go 絕對(duì)是我使用過(guò)的最不糟糕的的編程語(yǔ)言。在我寫作本文時(shí),我是想遏制我所看到的一種趨勢(shì),那就是過(guò)度使用 Go 的一些較復(fù)雜的部分。我仍然認(rèn)為 通道Channel可以更好,但是總體而言,Go 很棒。這就像你最喜歡的工具箱中有 這個(gè)工具;它可以有用途,它仍然可以成為你最喜歡的工具箱!

[[373820]]

更新:如果你是從一篇題為 《糟糕的 Go 語(yǔ)言》 的匯編文章看到這篇博文的話,那么我想表明的是,我很慚愧被列在這樣的名單上。Go 絕對(duì)是我使用過(guò)的最不糟糕的的編程語(yǔ)言。在我寫作本文時(shí),我是想遏制我所看到的一種趨勢(shì),那就是過(guò)度使用 Go 的一些較復(fù)雜的部分。我仍然認(rèn)為 通道Channel可以更好,但是總體而言,Go 很棒。這就像你最喜歡的工具箱中有 這個(gè)工具;它可以有用途(甚至還可能有更多的用途),它仍然可以成為你最喜歡的工具箱!

更新 2:如果我沒(méi)有指出這項(xiàng)對(duì)真實(shí)問(wèn)題的優(yōu)秀調(diào)查,那我將是失職的:《理解 Go 中的實(shí)際并發(fā)錯(cuò)誤》。這項(xiàng)調(diào)查的一個(gè)重要發(fā)現(xiàn)是...Go 通道會(huì)導(dǎo)致很多錯(cuò)誤。

從 2010 年中后期開始,我就斷斷續(xù)續(xù)地在使用 Google 的 Go 編程語(yǔ)言,自 2012 年 1 月開始(在 Go 1.0 之前!),我就用 Go 為 Space Monkey 編寫了合規(guī)的產(chǎn)品代碼。我對(duì) Go 的最初體驗(yàn)可以追溯到我在研究 Hoare 的 通信順序進(jìn)程 并發(fā)模型和 Matt Might 的 UCombinator 研究組 下的 π-演算 時(shí),作為我(現(xiàn)在已重定向)博士工作的一部分,以更好地支持多核開發(fā)。Go 就是在那時(shí)發(fā)布的(多么巧合?。。耶?dāng)即就開始學(xué)習(xí)嘗試了。

它很快就成為了 Space Monkey 開發(fā)的核心部分。目前,我們?cè)?Space Monkey 的生產(chǎn)系統(tǒng)有超過(guò) 42.5 萬(wàn)行的純 Go 代碼( 包括我們所有的 vendored 庫(kù)中的代碼量,這將使它接近 150 萬(wàn)行),所以也并不是你見過(guò)的最多的 Go 代碼,但是對(duì)于相對(duì)年輕的語(yǔ)言,我們是重度用戶。我們之前 寫了我們的 Go 使用情況。也開源了一些使用率很高的庫(kù);許多人似乎是我們的 OpenSSL 綁定(比 crypto/tls 更快,但請(qǐng)保持 openssl 本身是最新的!)、我們的 錯(cuò)誤處理庫(kù)、日志庫(kù) 和 度量標(biāo)準(zhǔn)收集庫(kù)/zipkin 客戶端 的粉絲。我們使用 Go、我們熱愛 Go、我們認(rèn)為它是目前為止我們使用過(guò)的最不糟糕的、符合我們需求的編程語(yǔ)言。

盡管我也不認(rèn)為我能說(shuō)服自己不要提及我的廣泛避免使用 goroutine-local-storage 庫(kù) (盡管它是一個(gè)你不應(yīng)該使用的魔改技巧,但它是一個(gè)漂亮的魔改),希望我的其他經(jīng)歷足以證明我在解釋我故意煽動(dòng)性的帖子標(biāo)題之前知道我在說(shuō)什么。

等等,什么?

如果你在大街上問(wèn)一個(gè)有名的程序員,Go 有什么特別之處? 她很可能會(huì)告訴你 Go 最出名的是通道Channels 和 goroutine。 Go 的理論基礎(chǔ)很大程度上是建立在 Hoare 的 CSP(通信順序進(jìn)程Communicating Sequential Processes)模型上的,該模型本身令人著迷且有趣,我堅(jiān)信,到目前為止,它產(chǎn)生的收益遠(yuǎn)遠(yuǎn)超過(guò)了我們的預(yù)期。

CSP(和 π-演算)都使用通信作為核心同步原語(yǔ),因此 Go 會(huì)有通道是有道理的。Rob Pike 對(duì) CSP 著迷(有充分的理由)相當(dāng)深 已經(jīng)有一段時(shí)間了。(當(dāng)時(shí) 和 現(xiàn)在)。

但是從務(wù)實(shí)的角度來(lái)看(也是 Go 引以為豪的),Go 把通道搞錯(cuò)了。在這一點(diǎn)上,通道的實(shí)現(xiàn)在我的書中幾乎是一個(gè)堅(jiān)實(shí)的反模式。為什么這么說(shuō)呢?親愛的讀者,讓我細(xì)數(shù)其中的方法。

你可能最終不會(huì)只使用通道

Hoare 的 “通信順序進(jìn)程” 是一種計(jì)算模型,實(shí)際上,唯一的同步原語(yǔ)是在通道上發(fā)送或接收的。一旦使用 互斥量mutex、信號(hào)量semaphore 或 條件變量condition variable、bam,你就不再處于純 CSP 領(lǐng)域。 Go 程序員經(jīng)常通過(guò)高呼 “通過(guò)交流共享內(nèi)存” 的 緩存的思想 來(lái)宣揚(yáng)這種模式和哲學(xué)。

那么,讓我們嘗試在 Go 中僅使用 CSP 編寫一個(gè)小程序!讓我們成為高分接收者。我們要做的就是跟蹤我們看到的最大的高分值。如此而已。

首先,我們將創(chuàng)建一個(gè) Game 結(jié)構(gòu)體。

  1. type Game struct {
  2. bestScore int
  3. scores chan int
  4. }

bestScore 不會(huì)受到互斥量mutex的保護(hù)!這很好,因?yàn)槲覀冎恍枰粋€(gè) goroutine 來(lái)管理其狀態(tài)并通過(guò)通道來(lái)接收新的分值即可。

  1. func (g *Game) run() {
  2. for score := range g.scores {
  3. if g.bestScore < score {
  4. g.bestScore = score
  5. }
  6. }
  7. }

好的,現(xiàn)在我們將創(chuàng)建一個(gè)有用的構(gòu)造函數(shù)來(lái)開始 Game

  1. func NewGame() (g *Game) {
  2. g = &Game{
  3. bestScore: 0,
  4. scores: make(chan int),
  5. }
  6. go g.run()
  7. return g
  8. }

接下來(lái),假設(shè)有人給了我們一個(gè)可以返回分?jǐn)?shù)的 Player。它也可能會(huì)返回錯(cuò)誤,因?yàn)榭赡軅魅氲?TCP 流可能會(huì)死掉或發(fā)生某些故障,或者玩家退出。

  1. type Player interface {
  2. NextScore() (score int, err error)
  3. }

為了處理 Player,我們假設(shè)所有錯(cuò)誤都是致命的,并將獲得的比分向下傳遞到通道。

  1. func (g *Game) HandlePlayer(p Player) error {
  2. for {
  3. score, err := p.NextScore()
  4. if err != nil {
  5. return err
  6. }
  7. g.scores <- score
  8. }
  9. }

好極了!現(xiàn)在我們有了一個(gè) Game 類型,可以以線程安全的方式跟蹤 Player 獲得的最高分?jǐn)?shù)。

你圓滿完成了自己的開發(fā)工作,并開始擁有客戶。你將這個(gè)游戲服務(wù)器公開,就取得了令人難以置信的成功!你的游戲服務(wù)器上也許正在創(chuàng)建許多游戲。

很快,你發(fā)現(xiàn)人們有時(shí)會(huì)離開你的游戲。許多游戲不再有任何玩家在玩,但沒(méi)有任何東西可以阻止游戲運(yùn)行的循環(huán)。死掉的 (*Game).run goroutines 讓你不知所措。

挑戰(zhàn): 在無(wú)需互斥量或 panics 的情況下修復(fù)上面的 goroutine 泄漏。實(shí)際上,可以滾動(dòng)到上面的代碼,并想出一個(gè)僅使用通道來(lái)解決此問(wèn)題的方案。

我等著。

就其價(jià)值而言,它完全可以只通過(guò)通道來(lái)完成,但是請(qǐng)觀察以下解決方案的簡(jiǎn)單性,它甚至沒(méi)有這個(gè)問(wèn)題:

  1. type Game struct {
  2. mtx sync.Mutex
  3. bestScore int
  4. }
  5.  
  6. func NewGame() *Game {
  7. return &Game{}
  8. }
  9.  
  10. func (g *Game) HandlePlayer(p Player) error {
  11. for {
  12. score, err := p.NextScore()
  13. if err != nil {
  14. return err
  15. }
  16. g.mtx.Lock()
  17. if g.bestScore < score {
  18. g.bestScore = score
  19. }
  20. g.mtx.Unlock()
  21. }
  22. }

你想選擇哪一個(gè)?不要被欺騙了,以為通道的解決方案可以使它在更復(fù)雜的情況下更具可讀性和可理解性。拆解Teardown是非常困難的。這種拆解若用互斥量mutex來(lái)做那只是小菜一碟,但最困難的是只使用 Go 專用通道來(lái)解決。另外,如果有人回復(fù)說(shuō)發(fā)送通道的通道更容易推理,我馬上就是感到頭疼。

重要的是,這個(gè)特殊的情況可能真的 很容易 解決,而通道有一些運(yùn)行時(shí)的幫助,而 Go 沒(méi)有提供!不幸的是,就目前的情況來(lái)看,與 Go 的 CSP 版本相比,使用傳統(tǒng)的同步原語(yǔ)synchronization primitives可以更好地解決很多問(wèn)題,這是令人驚訝的。稍后,我們將討論 Go 可以做些什么來(lái)簡(jiǎn)化此案例。

練習(xí): 還在懷疑? 試著讓上面兩種解決方案(只使用通道與只使用互斥量channel-only vs mutex-only)在一旦 bestScore 大于或等于 100 時(shí),就停止向 Players 索要分?jǐn)?shù)。繼續(xù)打開你的文本編輯器。這是一個(gè)很小的玩具問(wèn)題。

這里的總結(jié)是,如果你想做任何實(shí)際的事情,除了通道之外,你還會(huì)使用傳統(tǒng)的同步原語(yǔ)。

通道比你自己實(shí)現(xiàn)要慢一些

Go 如此重視 CSP 理論,我認(rèn)為其中一點(diǎn)就是,運(yùn)行時(shí)應(yīng)該可以通過(guò)通道做一些殺手級(jí)的調(diào)度優(yōu)化。也許通道并不總是最直接的原語(yǔ),但肯定是高效且快速的,對(duì)吧?

正如 Dustin Hiatt 在 Tyler Treat’s post about Go 上指出的那樣,

在幕后,通道使用鎖來(lái)序列化訪問(wèn)并提供線程安全性。 因此,通過(guò)使用通道同步對(duì)內(nèi)存的訪問(wèn),你實(shí)際上就是在使用鎖。 被包裝在線程安全隊(duì)列中的鎖。 那么,與僅僅使用標(biāo)準(zhǔn)庫(kù) sync 包中的互斥量相比,Go 的花式鎖又如何呢? 以下數(shù)字是通過(guò)使用 Go 的內(nèi)置基準(zhǔn)測(cè)試功能,對(duì)它們的單個(gè)集合連續(xù)調(diào)用 Put 得出的。

  1. > BenchmarkSimpleSet-8 3000000 391 ns/op
  2. > BenchmarkSimpleChannelSet-8 1000000 1699 ns/o
  3. >

無(wú)緩沖通道的情況與此類似,甚至是在爭(zhēng)用而不是串行運(yùn)行的情況下執(zhí)行相同的測(cè)試。

也許 Go 調(diào)度器會(huì)有所改進(jìn),但與此同時(shí),良好的舊互斥量和條件變量是非常好、高效且快速。如果你想要提高性能,請(qǐng)使用久經(jīng)考驗(yàn)的方法。

通道與其他并發(fā)原語(yǔ)組合不佳

好的,希望我已經(jīng)說(shuō)服了你,有時(shí)候,你至少還會(huì)與除了通道之外的原語(yǔ)進(jìn)行交互。標(biāo)準(zhǔn)庫(kù)似乎顯然更喜歡傳統(tǒng)的同步原語(yǔ)而不是通道。

你猜怎么著,正確地將通道與互斥量和條件變量一起使用,其實(shí)是有一定的挑戰(zhàn)性的。

關(guān)于通道的一個(gè)有趣的事情是,通道發(fā)送是同步的,這在 CSP 中是有很大意義的。通道發(fā)送和通道接收的目的是為了成為同步屏蔽,發(fā)送和接收應(yīng)該發(fā)生在同一個(gè)虛擬時(shí)間。如果你是在執(zhí)行良好的 CSP 領(lǐng)域,那就太好了。

實(shí)事求是地說(shuō),Go 通道也有多種緩沖方式。你可以分配一個(gè)固定的空間來(lái)考慮可能的緩沖,以便發(fā)送和接收是不同的事件,但緩沖區(qū)大小是有上限的。Go 并沒(méi)有提供一種方法來(lái)讓你擁有任意大小的緩沖區(qū) —— 你必須提前分配緩沖區(qū)大小。 這很好,我在郵件列表上看到有人在爭(zhēng)論,因?yàn)闊o(wú)論如何內(nèi)存都是有限的。

What。

這是個(gè)糟糕的答案。有各種各樣的理由來(lái)使用一個(gè)任意緩沖的通道。如果我們事先知道所有的事情,為什么還要使用 malloc 呢?

沒(méi)有任意緩沖的通道意味著在 任何 通道上的幼稚發(fā)送可能會(huì)隨時(shí)阻塞。你想在一個(gè)通道上發(fā)送,并在互斥下更新其他一些記賬嗎?小心!你的通道發(fā)送可能被阻塞!

  1. // ...
  2. s.mtx.Lock()
  3. // ...
  4. s.ch <- val // might block!
  5. s.mtx.Unlock()
  6. // ...

這是哲學(xué)家晚餐大戰(zhàn)的秘訣。如果你使用了鎖,則應(yīng)該迅速更新狀態(tài)并釋放它,并且盡可能不要在鎖下做任何阻塞。

有一種方法可以在 Go 中的通道上進(jìn)行非阻塞發(fā)送,但這不是默認(rèn)行為。假設(shè)我們有一個(gè)通道 ch := make(chan int),我們希望在其上無(wú)阻塞地發(fā)送值 1。以下是在不阻塞的情況下你必須要做的最小量的輸入:

  1. select {
  2. case ch <- 1: // it sent
  3. default: // it didn't
  4. }

對(duì)于剛?cè)腴T的 Go程序員來(lái)說(shuō),這并不是自然而然就能想到的事情。

綜上所述,因?yàn)橥ǖ郎系暮芏嗖僮鞫紩?huì)阻塞,所以需要對(duì)哲學(xué)家及其就餐仔細(xì)推理,才能在互斥量的保護(hù)下,成功地將通道操作與之并列使用,而不會(huì)造成死鎖。

嚴(yán)格來(lái)說(shuō),回調(diào)更強(qiáng)大,不需要不必要的 goroutines

每當(dāng) API 使用通道時(shí),或者每當(dāng)我指出通道使某些事情變得困難時(shí),總會(huì)有人會(huì)指出我應(yīng)該啟動(dòng)一個(gè) goroutine 來(lái)讀取該通道,并在讀取該通道時(shí)進(jìn)行所需的任何轉(zhuǎn)換或修復(fù)。

呃,不。如果我的代碼位于熱路徑中怎么辦?需要通道的實(shí)例很少,如果你的 API 可以設(shè)計(jì)為使用互斥量mutexes、信號(hào)量semaphores回調(diào)callbacks,而不使用額外的 goroutine (因?yàn)樗惺录吘壎际怯?API 事件觸發(fā)的),那么使用通道會(huì)迫使我在資源使用中添加另一個(gè)內(nèi)存分配堆棧。是的,goroutine 比線程輕得多,但更輕量并不意味著是最輕量。

正如我以前 在一篇關(guān)于使用通道的文章的評(píng)論中爭(zhēng)論過(guò)的(呵呵,互聯(lián)網(wǎng)),如果你使用回調(diào)而不是通道,你的 API 總是 可以更通用,總是 更靈活,而且占用的資源也會(huì)大大減少。“總是” 是一個(gè)可怕的詞,但我在這里是認(rèn)真的。有證據(jù)級(jí)的東西在進(jìn)行。

如果有人向你提供了一個(gè)基于回調(diào)的 API,而你需要一個(gè)通道,你可以提供一個(gè)回調(diào),在通道上發(fā)送,開銷不大,靈活性十足。

另一方面,如果有人提供了一個(gè)基于通道的 API 給你,而你需要一個(gè)回調(diào),你必須啟動(dòng)一個(gè) goroutine 來(lái)讀取通道,并且 你必須希望當(dāng)你完成讀取時(shí),沒(méi)有人試圖在通道上發(fā)送更多的東西,這樣你就會(huì)導(dǎo)致阻塞的 goroutine 泄漏。

對(duì)于一個(gè)超級(jí)簡(jiǎn)單的實(shí)際例子,請(qǐng)查看 context 接口(順便說(shuō)一下,它是一個(gè)非常有用的包,你應(yīng)該用它來(lái)代替 goroutine 本地存儲(chǔ))。

  1. type Context interface {
  2. ...
  3. // Done returns a channel that closes when this work unit should be canceled.
  4. // Done 返回一個(gè)通道,該通道在應(yīng)該取消該工作單元時(shí)關(guān)閉。
  5. Done() <-chan struct{}
  6.  
  7. // Err returns a non-nil error when the Done channel is closed
  8. // 當(dāng) Done 通道關(guān)閉時(shí),Err 返回一個(gè)非 nil 錯(cuò)誤
  9. Err() error
  10. ...
  11. }

想象一下,你要做的只是在 Done() 通道觸發(fā)時(shí)記錄相應(yīng)的錯(cuò)誤。你該怎么辦?如果你沒(méi)有在通道中選擇的好地方,則必須啟動(dòng) goroutine 進(jìn)行處理:

  1. go func() {
  2. <-ctx.Done()
  3. logger.Errorf("canceled: %v", ctx.Err())
  4. }()

如果 ctx 在不關(guān)閉返回 Done() 通道的情況下被垃圾回收怎么辦?哎呀!這正是一個(gè) goroutine 泄露!

現(xiàn)在假設(shè)我們更改了 Done 的簽名:

  1. // Done calls cb when this work unit should be canceled.
  2. Done(cb func())

首先,現(xiàn)在日志記錄非常容易。看看:ctx.Done(func() { log.Errorf ("canceled:%v", ctx.Err()) })。但是假設(shè)你確實(shí)需要某些選擇行為。你可以這樣調(diào)用它:

  1. ch := make(chan struct{})
  2. ctx.Done(func() { close(ch) })

瞧!通過(guò)使用回調(diào),不會(huì)失去表現(xiàn)力。 ch 的工作方式類似于用于返回的通道 Done(),在日志記錄的情況下,我們不需要啟動(dòng)整個(gè)新堆棧。我必須保留堆棧跟蹤信息(如果我們的日志包傾向于使用它們);我必須避免將其他堆棧分配和另一個(gè) goroutine 分配給調(diào)度程序。

下次你使用通道時(shí),問(wèn)問(wèn)你自己,如果你用互斥量和條件變量代替,是否可以消除一些 goroutine ? 如果答案是肯定的,那么修改這些代碼將更加有效。而且,如果你試圖使用通道只是為了在集合中使用 range 關(guān)鍵字,那么我將不得不請(qǐng)你放下鍵盤,或者只是回去編寫 Python 書籍。

通道 API 不一致,只是 cray-cray

在通道已關(guān)閉的情況下,執(zhí)行關(guān)閉或發(fā)送消息將會(huì)引發(fā) panics!為什么呢? 如果想要關(guān)閉通道,你需要在外部同步它的關(guān)閉狀態(tài)(使用互斥量等,這些互斥量的組合不是很好?。@樣其他寫入者才不會(huì)寫入或關(guān)閉已關(guān)閉的通道,或者只是向前沖,關(guān)閉或?qū)懭胍殃P(guān)閉的通道,并期望你必須恢復(fù)所有引發(fā)的 panics。

這是多么怪異的行為。 Go 中幾乎所有其他操作都有避免 panic 的方法(例如,類型斷言具有 , ok = 模式),但是對(duì)于通道,你只能自己動(dòng)手處理它。

好吧,所以當(dāng)發(fā)送失敗時(shí),通道會(huì)出現(xiàn) panic。我想這是有一定道理的。但是,與幾乎所有其他帶有 nil 值的東西不同,發(fā)送到 nil 通道不會(huì)引發(fā) panic。相反,它將永遠(yuǎn)阻塞!這很違反直覺(jué)。這可能是有用的行為,就像在你的除草器上附加一個(gè)開罐器,可能有用(在 Skymall 可以找到)一樣,但這肯定是意想不到的。與 nil 映射(執(zhí)行隱式指針解除引用),nil 接口(隱式指針解除引用),未經(jīng)檢查的類型斷言以及其他所有類型交互不同,nil 通道表現(xiàn)出實(shí)際的通道行為,就好像為該操作實(shí)例化了一個(gè)全新的通道一樣。

接收的情況稍微好一點(diǎn)。在已關(guān)閉的通道上執(zhí)行接收會(huì)發(fā)生什么?好吧,那會(huì)是有效操作——你將得到一個(gè)零值。好吧,我想這是有道理的。獎(jiǎng)勵(lì)!接收允許你在收到值時(shí)進(jìn)行 , ok = 樣式的檢查,以確定通道是否打開。謝天謝地,我們?cè)谶@里得到了 , ok =。

但是,如果你從 nil 渠道接收會(huì)發(fā)生什么呢? 也是永遠(yuǎn)阻塞! 耶!不要試圖利用這樣一個(gè)事實(shí):如果你關(guān)閉了通道,那么你的通道是 nil!

通道有什么好處?

當(dāng)然,通道對(duì)于某些事情是有好處的(畢竟它們是一個(gè)通用容器),有些事情你只能用它們來(lái)做(比如 select)。

它們是另一種特殊情況下的通用數(shù)據(jù)結(jié)構(gòu)

Go 程序員已經(jīng)習(xí)慣于對(duì)泛型的爭(zhēng)論,以至于我一提起這個(gè)詞就能感覺(jué)到 PTSD(創(chuàng)傷后應(yīng)激障礙)的到來(lái)。我不是來(lái)談?wù)撨@件事的,所以擦擦額頭上的汗,讓我們繼續(xù)前進(jìn)吧。

無(wú)論你對(duì)泛型的看法是什么,Go 的映射、切片和通道都是支持泛型元素類型的數(shù)據(jù)結(jié)構(gòu),因?yàn)樗鼈円呀?jīng)被特殊封裝到語(yǔ)言中了。

在一種不允許你編寫自己的泛型容器的語(yǔ)言中,任何允許你更好地管理事物集合的東西都是有價(jià)值的。在這里,通道是一個(gè)支持任意值類型的線程安全數(shù)據(jù)結(jié)構(gòu)。

所以這很有用!我想這可以省去一些陳詞濫調(diào)。

我很難把這算作是通道的勝利。

Select

使用通道可以做的主要事情是 select 語(yǔ)句。在這里,你可以等待固定數(shù)量的事件輸入。它有點(diǎn)像 epoll,但你必須預(yù)先知道要等待多少個(gè)套接字。

這是真正有用的語(yǔ)言功能。如果不是 select,通道將被徹底清洗。但是我的天吶,讓我告訴你,第一次決定可能需要在多個(gè)事物中選擇,但是你不知道有多少項(xiàng),因此必須使用 reflect.Select。

通道如何才能更好?

很難說(shuō) Go 語(yǔ)言團(tuán)隊(duì)可以為 Go 2.0 做的最具戰(zhàn)術(shù)意義的事情是什么(Go 1.0 兼容性保證很好,但是很費(fèi)勁),但這并不能阻止我提出一些建議。

在條件變量上的 Select !

我們可以不需要通道!這是我提議我們擺脫一些“圣牛sacred cows”(LCTT 譯注:神圣不可質(zhì)疑的事物)的地方,但是讓我問(wèn)你,如果你可以選擇任何自定義同步原語(yǔ),那會(huì)有多棒?(答:太棒了。)如果有的話,我們根本就不需要通道了。

GC 可以幫助我們嗎?

在第一個(gè)示例中,如果我們能夠使用定向類型的通道垃圾回收(GC)來(lái)幫助我們進(jìn)行清理,我們就可以輕松地解決通道的高分服務(wù)器清理問(wèn)題。

如你所知,Go 具有定向類型的通道。 你可以使用僅支持讀取的通道類型(<-chan)和僅支持寫入的通道類型(chan<-)。 這太棒了!

Go 也有垃圾回收功能。 很明顯,某些類型的記賬方式太繁瑣了,我們不應(yīng)該讓程序員去處理它們。 我們清理未使用的內(nèi)存! 垃圾回收非常有用且整潔。

那么,為什么不幫助清理未使用或死鎖的通道讀取呢? 與其讓 make(chan Whatever) 返回一個(gè)雙向通道,不如讓它返回兩個(gè)單向通道(chanReader, chanWriter:= make(chan Type))。

讓我們重新考慮一下最初的示例:

  1. type Game struct {
  2. bestScore int
  3. scores chan<- int
  4. }
  5.  
  6. func run(bestScore *int, scores <-chan int) {
  7. // 我們不會(huì)直接保留對(duì)游戲的引用,因?yàn)檫@樣我們就會(huì)保留著通道的發(fā)送端。
  8. for score := range scores {
  9. if *bestScore < score {
  10. *bestScore = score
  11. }
  12. }
  13. }
  14.  
  15. func NewGame() (g *Game) {
  16. // 這種 make(chan) 返回風(fēng)格是一個(gè)建議
  17. scoreReader, scoreWriter := make(chan int)
  18. g = &Game{
  19. bestScore: 0,
  20. scores: scoreWriter,
  21. }
  22. go run(&g.bestScore, scoreReader)
  23. return g
  24. }
  25.  
  26. func (g *Game) HandlePlayer(p Player) error {
  27. for {
  28. score, err := p.NextScore()
  29. if err != nil {
  30. return err
  31. }
  32. g.scores <- score
  33. }
  34. }

如果垃圾回收關(guān)閉了一個(gè)通道,而我們可以證明它永遠(yuǎn)不會(huì)有更多的值,那么這個(gè)解決方案是完全可行的。是的,是的,run 中的評(píng)論暗示著有一把相當(dāng)大的槍瞄準(zhǔn)了你的腳,但至少現(xiàn)在這個(gè)問(wèn)題可以很容易地解決了,而以前確實(shí)不是這樣。此外,一個(gè)聰明的編譯器可能會(huì)做出適當(dāng)?shù)淖C明,以減少這種腳槍造成的損害。

其他小問(wèn)題

  • Dup 通道嗎? —— 如果我們可以在通道上使用等效于 dup 的系統(tǒng)調(diào)用,那么我們也可以很容易地解決多生產(chǎn)者問(wèn)題。 每個(gè)生產(chǎn)者可以關(guān)閉自己的 dup 版通道,而不會(huì)破壞其他生產(chǎn)者。
  • 修復(fù)通道 API! —— 關(guān)閉不是冪等的嗎? 在已關(guān)閉的通道上發(fā)送信息引起的 panics 沒(méi)有辦法避免嗎? 啊!
  • 任意緩沖的通道 —— 如果我們可以創(chuàng)建沒(méi)有固定的緩沖區(qū)大小限制的緩沖通道,那么我們可以創(chuàng)建非阻塞的通道。

那我們?cè)撛趺聪虼蠹医榻B Go 呢?

如果你還沒(méi)有,請(qǐng)看看我目前最喜歡的編程文章:《你的函數(shù)是什么顏色》。雖然不是專門針對(duì) Go,但這篇博文比我更有說(shuō)服力地闡述了為什么 goroutines 是 Go 最好的特性(這也是 Go 在某些應(yīng)用程序中優(yōu)于 Rust 的方式之一)。

如果你還在使用這樣的一種編程語(yǔ)言寫代碼,它強(qiáng)迫你使用類似 yield 關(guān)鍵字來(lái)獲得高性能、并發(fā)性或事件驅(qū)動(dòng)的模型,那么你就是活在過(guò)去,不管你或其他人是否知道這一點(diǎn)。到目前為止,Go 是我所見過(guò)的實(shí)現(xiàn) M:N 線程模型(非 1:1 )的語(yǔ)言中最好的入門者之一,而且這種模型非常強(qiáng)大。

所以,跟大家說(shuō)說(shuō) goroutines 吧。

如果非要我選擇 Go 的另一個(gè)主要特性,那就是接口。靜態(tài)類型的 鴨子模型duck typing 使得擴(kuò)展、使用你自己或他人的項(xiàng)目變得如此有趣而令人驚奇,這也許值得我改天再寫一組完全不同的文章來(lái)介紹它。

所以…

我一直看到人們爭(zhēng)先恐后沖進(jìn) Go,渴望充分利用通道來(lái)發(fā)揮其全部潛力。這是我對(duì)你的建議。

夠了!

當(dāng)你在編寫 API 和接口時(shí),盡管“絕不”的建議可能很糟糕,但我非常肯定,通道從來(lái)沒(méi)有什么時(shí)候好過(guò),我用過(guò)的每一個(gè)使用通道的 Go API,最后都不得不與之抗?fàn)?。我從?lái)沒(méi)有想過(guò)“哦 太好了,這里是一個(gè)通道;”它總是被一些變體取代,這是什么新鮮的地獄?

所以,請(qǐng)?jiān)谶m當(dāng)?shù)牡胤剑⑶抑辉谶m當(dāng)?shù)牡胤绞褂猛ǖ馈?/em>

在我使用的所有 Go 代碼中,我可以用一只手?jǐn)?shù)出有多少次通道真的是最好的選擇。有時(shí)候是這樣的。那很好!那就用它們吧。但除此之外,就不要再使用了。 

責(zé)任編輯:龐桂玉 來(lái)源: Linux中國(guó)
相關(guān)推薦

2018-12-29 14:45:34

RESTfulGoogleUser

2018-01-17 22:17:16

IT架構(gòu)數(shù)據(jù)糟糕架構(gòu)

2012-07-16 11:27:08

項(xiàng)目開發(fā)

2012-07-16 09:41:59

項(xiàng)目

2021-11-26 22:14:55

PHP編程語(yǔ)言開發(fā)

2022-09-14 09:37:17

JavaScript默認(rèn)導(dǎo)出

2013-06-21 14:02:19

軟件開發(fā)方法

2009-12-09 09:48:23

IT市場(chǎng)失敗事件

2023-01-05 08:34:48

JDK工具

2009-08-24 09:20:18

2021-08-02 08:21:53

Python編程語(yǔ)言開發(fā)

2012-12-28 09:47:07

程序員代碼編程

2020-06-03 15:28:11

QQ新模式簡(jiǎn)潔

2013-09-05 10:33:09

福布斯微軟諾基亞

2013-10-23 10:51:48

開發(fā)模型軟件開發(fā)軟件產(chǎn)業(yè)

2011-08-05 17:11:42

Amazon

2020-05-13 17:15:49

CPUPC處理器

2021-10-15 10:24:04

Windows 11操作系統(tǒng)微軟

2013-09-29 13:40:21

項(xiàng)目

2022-01-17 19:00:28

LinuxWindows微軟
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)