當(dāng)談到并發(fā)時(shí)删窒,許多編程語言采用共享內(nèi)存/狀態(tài)模型。然而顺囊,Go 通過實(shí)現(xiàn)通信順序進(jìn)程 (CSP) 來區(qū)別自己。在CSP中蕉拢,一個(gè)程序由并行進(jìn)程組成特碳,這些進(jìn)程不共享狀態(tài);相反晕换,它們使用通道來通信并同步它們的行為午乓。
因此,對于有興趣采用 Go 的開發(fā)者來說闸准,理解通道的工作原理變得至關(guān)重要益愈。在這篇文章中,我將使用 Gophers 運(yùn)行他們的想象咖啡店的有趣類比來描述通道夷家,因?yàn)槲覉?jiān)信人們更容易通過視覺來學(xué)習(xí)蒸其。
場景
Partier、Candier 和 Stringer 正在經(jīng)營一個(gè)咖啡館库快。由于制作咖啡所需的時(shí)間比接受訂單要長摸袁,Partier 會(huì)協(xié)助接受顧客的訂單,然后將這些訂單傳遞給廚房义屏,Candier 和 Stringer 在那里準(zhǔn)備咖啡靠汁。
無緩沖通道
最初,咖啡店以最簡單的方式運(yùn)作:每當(dāng)收到一個(gè)新訂單時(shí)闽铐,Partier 將訂單放入通道蝶怔,并等待 Candier 或 Stringer 拿走它,然后再接受任何新訂單兄墅。通過無緩沖通道踢星,使用 ch := make(chan Order)
創(chuàng)建,實(shí)現(xiàn)了 Partier 和廚房之間的這種溝通隙咸。當(dāng)通道中沒有掛起的訂單時(shí)斩狱,即使 Stringer 和 Candier 都準(zhǔn)備好接受新訂單耳高,他們?nèi)匀惶幱诘却隣顟B(tài),等待新訂單到來所踊。
當(dāng)收到一個(gè)新訂單時(shí)泌枪,Partier 將其放入通道,使該訂單可以被 Candier 或 Stringer 拿走秕岛。但在接受新訂單之前碌燕,Partier 必須等待其中一個(gè)人從通道中取出訂單。
當(dāng) Stringer 和 Candier 都準(zhǔn)備好接受新訂單時(shí)继薛,新訂單將立即被其中一個(gè)拿走修壕。然而,不能保證或預(yù)測到底是誰會(huì)拿到訂單遏考。Stringer 和 Candier 之間的選擇是不確定的慈鸠,這取決于諸如調(diào)度和 Go 運(yùn)行時(shí)的內(nèi)部機(jī)制之類的因素。假設(shè) Candier 得到了這個(gè)第一個(gè)訂單灌具。
在 Candier 完成處理第一個(gè)訂單后青团,她回到等待狀態(tài)。如果沒有新的訂單到來咖楣,兩個(gè)工人督笆,Candier 和 Stringer,都會(huì)閑置诱贿,直到 Partier 將另一個(gè)訂單放入通道供他們處理娃肿。
當(dāng)收到一個(gè)新訂單,且 Stringer 和 Candier 都可以處理它時(shí)珠十。即使 Candier 剛剛處理了上一個(gè)訂單料扰,接收新訂單的特定工人仍然是不確定的。在這個(gè)場景中焙蹭,我們假設(shè) Candier 再次被分配了這第二個(gè)訂單记罚。
新的訂單 order3
到來,由于 Candier 正在處理 order2
壳嚎,她并沒有等在 order := <-ch
這一行桐智,Stringer 成為了唯一可以接收 order3
的可用工人。因此烟馅,他會(huì)得到它说庭。
在 order3
發(fā)送給 Stringer 之后,order4
立即到達(dá)郑趁。此時(shí)刊驴,Stringer 和 Candier 都已經(jīng)在處理他們各自的訂單,沒有人可以拿走 order4
。因?yàn)橥ǖ罌]有緩沖捆憎,將 order4
放入通道會(huì)阻塞 Partier舅柜,直到 Stringer 或 Candier 有一個(gè)變得可以接受 order4
。我經(jīng)扯愣瑁看到人們對無緩沖通道(用 make(chan order)
或 make(chan order, 0)
創(chuàng)建)和緩沖大小為1的通道(用 make(chan order, 1)
創(chuàng)建)感到困惑致份。因此,他們錯(cuò)誤地期望 ch <- order4
立即完成础拨,允許 Partier 接受 order5
氮块,然后在 ch <- order5
上被阻塞。如果你也有這種想法诡宗,我在 Go Playground 上創(chuàng)建了一個(gè)代碼片段滔蝉,幫助你糾正誤解 https://go.dev/play/p/shRNiDDJYB4。
緩沖通道
無緩沖通道確實(shí)可以工作塔沃,但它限制了整體的吞吐量蝠引。如果他們僅接受一定數(shù)量的訂單在后臺(tái)(廚房)順序處理會(huì)更好。這可以通過緩沖通道來實(shí)現(xiàn)≈瘢現(xiàn)在螃概,即使 Stringer 和 Candier 忙于處理他們的訂單,只要通道沒有滿名扛,Partier 仍然可以在通道中留下新的訂單并繼續(xù)接受其他訂單,例如茧痒,最多3個(gè)掛起的訂單肮韧。
通過引入緩沖通道,咖啡店增強(qiáng)了處理更多訂單的能力旺订。但是弄企,選擇適當(dāng)?shù)木彌_大小以保持合理的客戶等待時(shí)間是至關(guān)重要的。畢竟区拳,沒有客戶愿意忍受過長的等待時(shí)間拘领。有時(shí),拒絕新訂單可能比接受它們并且無法及時(shí)完成它們更為可取樱调。此外约素,在短暫的容器化(Docker)應(yīng)用程序中使用緩沖通道時(shí)必須小心,因?yàn)轭A(yù)期會(huì)有隨機(jī)重啟笆凌,在這種情況下圣猎,從通道中恢復(fù)消息可能是一項(xiàng)具有挑戰(zhàn)性的任務(wù),甚至可能是不可能的乞而。
通道與阻塞隊(duì)列
盡管它們在本質(zhì)上是不同的送悔,但Java中的Blocking Queue是用來在線程之間通信的,而Go中的Channel是用于Goroutine的通信,BlockingQueue 和 Channel 的行為在某種程度上是相似的欠啤。如果你熟悉BlockingQueue荚藻,那么理解Channel肯定會(huì)很容易。
常見用途
通道是Go應(yīng)用中的一個(gè)基礎(chǔ)且廣泛使用的功能洁段,服務(wù)于各種目的应狱。通道的一些常見用途包括:
- Goroutine 通信:通道使不同的goroutines之間能夠進(jìn)行消息交換,使它們可以協(xié)作而無需直接共享狀態(tài)眉撵。
- 工作池:正如上面的示例中所見侦香,通道經(jīng)常用于管理工作池,其中多個(gè)相同的工作人員從共享通道處理傳入的任務(wù)纽疟。
- 扇出罐韩,扇入:通道促進(jìn)了扇出,扇入模式污朽,其中多個(gè)goroutines(扇出)執(zhí)行工作并將結(jié)果發(fā)送到一個(gè)通道散吵,而另一個(gè)goroutine(扇入)消費(fèi)這些結(jié)果。
-
超時(shí)和截止日期:與
select
語句結(jié)合使用蟆肆,通道可以用于處理超時(shí)和截止日期矾睦,確保程序可以優(yōu)雅地處理延遲并避免無限的等待。
我將在其他文章中更詳細(xì)地探討通道的不同用途炎功。但是枚冗,現(xiàn)在,讓我們通過實(shí)現(xiàn)上述的咖啡店場景來結(jié)束這篇入門博客蛇损,并親眼看到通道如何在實(shí)踐中工作赁温。我們將探索Partier、Candier和Stringer之間的互動(dòng)淤齐,并觀察通道如何促進(jìn)他們之間的順暢溝通和協(xié)調(diào)股囊,使咖啡店能夠有效地處理訂單和同步。
給我看你的代碼更啄!
package main
import (
"fmt"
"log"
"math/rand"
"sync"
"time"
)
func main() {
ch := make(chan order, 3)
wg := &sync.WaitGroup{} // More on WaitGroup another day
wg.Add(2)
go func() {
defer wg.Done()
worker("Candier", ch)
}()
go func() {
defer wg.Done()
worker("Stringer", ch)
}()
for i := 0; i < 10; i++ {
waitForOrders()
o := order(i)
log.Printf("Partier: I %v, I will pass it to the channel\n", o)
ch <- o
}
log.Println("No more orders, closing the channel to signify workers to stop")
close(ch)
log.Println("Wait for workers to gracefully stop")
wg.Wait()
log.Println("All done")
}
func waitForOrders() {
processingTime := time.Duration(rand.Intn(2)) * time.Second
time.Sleep(processingTime)
}
func worker(name string, ch <-chan order) {
for o := range ch {
log.Printf("%s: I got %v, I will process it\n", name, o)
processOrder(o)
log.Printf("%s: I completed %v, I'm ready to take a new order\n", name, o)
}
log.Printf("%s: I'm done\n", name)
}
func processOrder(_ order) {
processingTime := time.Duration(2+rand.Intn(2)) * time.Second
time.Sleep(processingTime)
}
type order int
func (o order) String() string {
return fmt.Sprintf("order-%02d", o)
}
您可以復(fù)制這段代碼稚疹,在您的IDE上進(jìn)行調(diào)整并運(yùn)行它,以更好地理解通道是如何工作的祭务。
如果您對保持對軟件工程領(lǐng)域的最新動(dòng)態(tài)感興趣内狗,請關(guān)注我。我將確保讓您了解最新信息义锥!