在 Go 語言中,chan(通道)是用于在不同 goroutine 之間進(jìn)行通信和同步的重要機(jī)制。它的設(shè)計(jì)和實(shí)現(xiàn)允許在并發(fā)編程中安全、有效地傳遞數(shù)據(jù)。以下是 chan 的工作原理和實(shí)現(xiàn)細(xì)節(jié)
通道有類型,指定了通道能夠傳遞的數(shù)據(jù)類型。例如,chan int 是一個(gè)只能傳遞整數(shù)的通道。
沒有緩沖區(qū)的通道,發(fā)送和接收操作是同步的,即發(fā)送操作會(huì)阻塞直到有接收操作發(fā)生。
具有一定緩沖區(qū)的通道,發(fā)送操作在緩沖區(qū)未滿時(shí)不會(huì)阻塞,直到緩沖區(qū)滿時(shí)才會(huì)阻塞。
通道在內(nèi)部是通過 hchan 結(jié)構(gòu)體來實(shí)現(xiàn)的。這個(gè)結(jié)構(gòu)體包含了通道的基本信息和狀態(tài)
type hchan struct { qcount uint // 緩沖區(qū)中數(shù)據(jù)的數(shù)量 dataqsiz uint // 緩沖區(qū)的大小 buf unsafe.Pointer // 緩沖區(qū)指針 elemsize uint16 // 元素的大小 closed uint32 // 通道是否關(guān)閉 sendx uint // 發(fā)送操作的索引 recvx uint // 接收操作的索引 recvq waitq // 等待接收的 goroutine 隊(duì)列 sendq waitq // 等待發(fā)送的 goroutine 隊(duì)列 lock mutex // 保護(hù)通道的互斥鎖}
如果沒有接收者,發(fā)送方會(huì)阻塞,直到有接收方開始接收。
如果沒有發(fā)送者,接收方會(huì)阻塞,直到有發(fā)送方開始發(fā)送。
如果緩沖區(qū)未滿,數(shù)據(jù)直接寫入緩沖區(qū)。若緩沖區(qū)已滿,發(fā)送方會(huì)阻塞,直到有空間可用。
如果緩沖區(qū)不為空,數(shù)據(jù)直接從緩沖區(qū)讀取。若緩沖區(qū)為空,接收方會(huì)阻塞,直到有數(shù)據(jù)可讀。
通道的發(fā)送和接收操作都是原子性的,并且由互斥鎖保護(hù)。這確保了多個(gè) goroutine 同時(shí)操作通道時(shí)不會(huì)發(fā)生競態(tài)條件。
每個(gè)通道都有一個(gè)互斥鎖,用于保護(hù)通道的狀態(tài)和數(shù)據(jù)。
通道維護(hù)兩個(gè)等待隊(duì)列,一個(gè)用于等待接收的 goroutine,一個(gè)用于等待發(fā)送的 goroutine。當(dāng)發(fā)送或接收操作不能立即完成時(shí),goroutine 會(huì)被加入相應(yīng)的等待隊(duì)列中。
通過調(diào)用 close(chan) 可以關(guān)閉通道。關(guān)閉操作會(huì)設(shè)置通道的 closed 標(biāo)志,并喚醒所有在通道上阻塞的發(fā)送和接收操作。
向已關(guān)閉的通道發(fā)送數(shù)據(jù)會(huì)引發(fā) panic,從已關(guān)閉的通道接收數(shù)據(jù)會(huì)立即返回零值。
以下是通道發(fā)送和接收操作的一些實(shí)現(xiàn)細(xì)節(jié)
chan send 檢查通道是否關(guān)閉,如果沒有接收者且緩沖區(qū)未滿,數(shù)據(jù)會(huì)被直接寫入緩沖區(qū),否則會(huì)阻塞當(dāng)前 goroutine 并將其加入 sendq。
chan recv 檢查通道是否關(guān)閉或緩沖區(qū)是否為空,如果有數(shù)據(jù)則直接返回,否則阻塞當(dāng)前 goroutine 并將其加入 recvq。
Go 語言中的通道通過上述機(jī)制實(shí)現(xiàn)了 goroutine 之間的安全、高效通信。通道的設(shè)計(jì)考慮了并發(fā)編程中的同步問題,通過緩沖機(jī)制和等待隊(duì)列的管理,使得數(shù)據(jù)傳遞和同步操作都能高效地進(jìn)行。
在 Go 語言中,可以通過 make 函數(shù)來定義通道。根據(jù)是否指定緩沖區(qū)大小,可以創(chuàng)建無緩沖區(qū)通道和有緩沖區(qū)通道。以下是具體的定義和示例:
無緩沖區(qū)通道是指在沒有緩沖區(qū)的情況下,發(fā)送和接收操作是同步的。發(fā)送操作會(huì)一直阻塞,直到有接收者接收數(shù)據(jù)。
ch := make(chan int)
package mainimport ( "fmt")func main() { ch := make(chan int) // 啟動(dòng)一個(gè) goroutine 發(fā)送數(shù)據(jù) go func() { ch <- 42 // 發(fā)送操作會(huì)阻塞,直到有接收者 }() // 接收數(shù)據(jù) value := <-ch fmt.Println(value) // 輸出: 42}
在這個(gè)例子中,ch 是一個(gè)無緩沖區(qū)通道,發(fā)送操作 ch <- 42 會(huì)阻塞,直到主 goroutine 執(zhí)行 <-ch 接收數(shù)據(jù)。
有緩沖區(qū)通道允許在緩沖區(qū)未滿時(shí)發(fā)送操作不會(huì)阻塞,直到緩沖區(qū)滿時(shí)才會(huì)阻塞。
ch := make(chan int, 3) // 創(chuàng)建一個(gè)緩沖區(qū)大小為 3 的通道
package mainimport ( "fmt")func main() { ch := make(chan int, 3) // 定義緩沖區(qū)大小為 3 的通道 // 發(fā)送數(shù)據(jù)到通道,不會(huì)阻塞 ch <- 1 ch <- 2 ch <- 3 // 緩沖區(qū)已滿,下面的發(fā)送操作會(huì)阻塞,直到有接收者 go func() { ch <- 4 }() // 接收數(shù)據(jù) fmt.Println(<-ch) // 輸出: 1 fmt.Println(<-ch) // 輸出: 2 fmt.Println(<-ch) // 輸出: 3 fmt.Println(<-ch) // 輸出: 4}
在這個(gè)例子中,ch 是一個(gè)有緩沖區(qū)通道,緩沖區(qū)大小為 3。前 3 個(gè)發(fā)送操作不會(huì)阻塞,直到緩沖區(qū)滿后,第 4 個(gè)發(fā)送操作會(huì)阻塞,直到有接收者開始接收數(shù)據(jù)。
通過 make(chan T) 可以創(chuàng)建無緩沖區(qū)通道,通過 make(chan T, capacity) 可以創(chuàng)建有緩沖區(qū)通道。無緩沖區(qū)通道在發(fā)送和接收操作上是同步的,而有緩沖區(qū)通道允許在緩沖區(qū)未滿時(shí)進(jìn)行非阻塞的發(fā)送操作。通過以上示例,可以清晰地看到兩種通道的行為差異。
在 Go 語言中,select 語句用于處理多個(gè)通道的通信操作。它的作用是讓 goroutine 可以同時(shí)等待多個(gè)通道操作(發(fā)送或接收),并在其中任何一個(gè)通道操作完成時(shí)執(zhí)行相應(yīng)的分支代碼。select 語句的使用使得在處理并發(fā)編程時(shí)更加靈活和高效。
select 語句的語法與 switch 語句類似,但它專門用于通道操作。每個(gè) case 分支包含一個(gè)通道操作(發(fā)送或接收),select 會(huì)選擇其中一個(gè)已準(zhǔn)備好的通道操作進(jìn)行處理。
select {case expr1: // 如果 expr1 通道操作可以進(jìn)行,則執(zhí)行此分支case expr2: // 如果 expr2 通道操作可以進(jìn)行,則執(zhí)行此分支default: // 如果沒有任何通道操作可以進(jìn)行,則執(zhí)行此分支}
以下是一個(gè)使用 select 語句的示例:
package mainimport ( "fmt" "time")func main() { ch1 := make(chan string) ch2 := make(chan string) // 啟動(dòng)第一個(gè) goroutine go func() { time.Sleep(2 * time.Second) ch1 <- "message from ch1" }() // 啟動(dòng)第二個(gè) goroutine go func() { time.Sleep(1 * time.Second) ch2 <- "message from ch2" }() for i := 0; i < 2; i++ { select { case msg1 := <-ch1: fmt.Println(msg1) case msg2 := <-ch2: fmt.Println(msg2) } }}
在這個(gè)例子中,有兩個(gè)通道 ch1 和 ch2,每個(gè)通道都在不同的 goroutine 中發(fā)送消息。select 語句使得主 goroutine 可以同時(shí)等待兩個(gè)通道的消息,并在任意一個(gè)通道接收到消息時(shí)執(zhí)行相應(yīng)的分支。
如果在 select 語句中添加了 default 分支,當(dāng)所有通道操作都無法立即進(jìn)行時(shí),會(huì)執(zhí)行 default 分支。這樣可以避免 select 語句阻塞。
package mainimport ( "fmt" "time")func main() { ch := make(chan string) go func() { time.Sleep(2 * time.Second) ch <- "message" }() for { select { case msg := <-ch: fmt.Println(msg) return default: fmt.Println("No message received, doing other work") time.Sleep(500 * time.Millisecond) } }}
在這個(gè)例子中,如果通道 ch 上沒有消息可接收,select 會(huì)執(zhí)行 default 分支,打印一條消息并繼續(xù)執(zhí)行其他工作。
select 語句是 Go 語言中處理并發(fā)編程的重要工具,通過它可以同時(shí)等待多個(gè)通道操作并在其中一個(gè)操作完成時(shí)進(jìn)行相應(yīng)處理。select 提供了一種靈活且高效的方式來處理多個(gè)通道之間的通信,使得并發(fā)程序的設(shè)計(jì)更加簡潔和直觀。
在 Go 語言的 select 語句中,如果有多個(gè)通道操作同時(shí)準(zhǔn)備就緒(即都可以進(jìn)行),Go 運(yùn)行時(shí)會(huì)從這些通道操作中隨機(jī)選擇一個(gè)執(zhí)行。一旦某個(gè)通道操作被選中并執(zhí)行,其它通道的等待操作將不會(huì)繼續(xù)進(jìn)行。每次執(zhí)行 select 語句時(shí)都會(huì)重新評(píng)估所有通道操作。
為了更好地理解這個(gè)機(jī)制,以下是一個(gè)示例,展示當(dāng)多個(gè)通道同時(shí)準(zhǔn)備就緒時(shí),select 語句的行為:
package mainimport ( "fmt" "time")func main() { ch1 := make(chan string) ch2 := make(chan string) ch3 := make(chan string) go func() { time.Sleep(1 * time.Second) ch1 <- "message from ch1" }() go func() { time.Sleep(1 * time.Second) ch2 <- "message from ch2" }() go func() { time.Sleep(1 * time.Second) ch3 <- "message from ch3" }() for i := 0; i < 3; i++ { select { case msg1 := <-ch1: fmt.Println(msg1) case msg2 := <-ch2: fmt.Println(msg2) case msg3 := <-ch3: fmt.Println(msg3) } }}
在這個(gè)示例中,有三個(gè)通道 ch1, ch2, 和 ch3,每個(gè)通道在 1 秒后發(fā)送一個(gè)消息。因?yàn)樗型ǖ涝谕粫r(shí)間準(zhǔn)備就緒,select 語句將從中隨機(jī)選擇一個(gè)進(jìn)行處理,并打印相應(yīng)的消息。每次循環(huán)都會(huì)重新評(píng)估所有通道。
當(dāng) select 語句等待多個(gè)通道時(shí),如果其中一個(gè)通道操作可以進(jìn)行,其它通道的操作不會(huì)繼續(xù)等待,而是等待下一次 select 語句的評(píng)估。每次 select 語句執(zhí)行時(shí)都會(huì)重新評(píng)估所有通道操作,并選擇其中一個(gè)可以進(jìn)行的操作。如果多個(gè)通道同時(shí)就緒,select 會(huì)隨機(jī)選擇其中一個(gè)進(jìn)行處理。
本文鏈接:http://www.www897cc.com/showinfo-26-91354-0.html深入Go原理:協(xié)程間通信基礎(chǔ)Chan
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com