wordpress掃碼槍鄭州好的seo外包公司
無緩沖的 channel 和有緩沖的 channel 的區(qū)別?
在 Go 語言中,channel 是用來在 goroutines 之間傳遞數(shù)據(jù)的主要機(jī)制。它們有兩種類型:無緩沖的 channel 和有緩沖的 channel。
- 無緩沖的 channel
行為:無緩沖的 channel 是一種同步的通信方式,發(fā)送和接收必須同時(shí)發(fā)生。如果一個(gè) goroutine 試圖通過無緩沖 channel 發(fā)送數(shù)據(jù),它會(huì)阻塞,直到另一個(gè) goroutine 從該 channel 中接收數(shù)據(jù)。反之亦然,接收方在準(zhǔn)備好接收數(shù)據(jù)之前,發(fā)送方無法繼續(xù)執(zhí)行。
用法:適合在兩個(gè) goroutines 之間實(shí)現(xiàn)精確的同步,確保它們在同一時(shí)刻傳遞數(shù)據(jù)。
優(yōu)點(diǎn):
保證了發(fā)送和接收的同步,可以避免某些類型的并發(fā)錯(cuò)誤。
更簡單,適合需要嚴(yán)格按順序處理任務(wù)的場景。
缺點(diǎn):
性能上可能存在瓶頸,因?yàn)楸仨毜却龑?yīng)的發(fā)送或接收操作才能繼續(xù)執(zhí)行。
無緩沖的 Channel 示例
package mainimport ("fmt""time"
)func main() {// 創(chuàng)建一個(gè)無緩沖的 channelch := make(chan int)// 啟動(dòng)一個(gè) goroutine 來接收數(shù)據(jù)go func() {// 接收數(shù)據(jù)之前會(huì)阻塞,直到 main goroutine 發(fā)送數(shù)據(jù)val := <-chfmt.Println("接收到的數(shù)據(jù):", val)}()// 模擬一些操作time.Sleep(1 * time.Second)// 發(fā)送數(shù)據(jù)到 channel,會(huì)阻塞直到接收方讀取數(shù)據(jù)ch <- 42fmt.Println("數(shù)據(jù)已發(fā)送")
}
由于是無緩沖的 channel,main goroutine 在發(fā)送 42 時(shí)會(huì)阻塞,直到 goroutine 從 channel 中接收到這個(gè)值,程序才會(huì)繼續(xù)執(zhí)行。
2. 有緩沖的 channel
行為:有緩沖的 channel 容許在 channel 中存儲(chǔ)一定數(shù)量的數(shù)據(jù)元素,發(fā)送方可以在 channel 未滿時(shí)繼續(xù)發(fā)送數(shù)據(jù),而無需等待接收方。接收方只有當(dāng) channel 非空時(shí)才會(huì)接收數(shù)據(jù)。
用法:適合發(fā)送方和接收方的處理速度不一致的情況,允許發(fā)送方先發(fā)送一部分?jǐn)?shù)據(jù),接收方稍后接收。
優(yōu)點(diǎn):
提供了一定的并發(fā)靈活性,發(fā)送方可以在接收方未準(zhǔn)備好時(shí)先發(fā)送一定數(shù)量的數(shù)據(jù)。
提高了性能,減少了因?yàn)橥阶枞鴮?dǎo)致的性能損耗。
缺點(diǎn):
如果緩沖區(qū)設(shè)計(jì)不當(dāng),可能會(huì)出現(xiàn)緩沖區(qū)溢出或浪費(fèi)資源的情況。
由于有緩沖的存在,可能會(huì)導(dǎo)致發(fā)送和接收之間的時(shí)間不同步,增加調(diào)試和排查問題的難度。
總結(jié)
無緩沖的 channel 強(qiáng)調(diào)的是同步性,適合需要嚴(yán)格同步的場景。
有緩沖的 channel 提供更多的靈活性,允許在并發(fā)處理中有更大的自由度,不需要完全同步。
在使用中,可以根據(jù)具體場景選擇合適的 channel 類型。例如,在生產(chǎn)者-消費(fèi)者模型中,有緩沖的 channel 可以防止生產(chǎn)者等待消費(fèi)者處理;而在需要精確同步的任務(wù)中,無緩沖 channel 則更加合適。
有緩沖的 Channel 示例
package mainimport ("fmt""time"
)func main() {// 創(chuàng)建一個(gè)帶有緩沖區(qū)大小為 2 的 channelch := make(chan int, 2)// 發(fā)送兩個(gè)數(shù)據(jù)到 channelch <- 1fmt.Println("發(fā)送了數(shù)據(jù) 1")ch <- 2fmt.Println("發(fā)送了數(shù)據(jù) 2")// 此時(shí),由于緩沖區(qū)還有空間,發(fā)送不會(huì)阻塞go func() {// 延遲讀取,模擬一些操作time.Sleep(2 * time.Second)val := <-chfmt.Println("接收到的數(shù)據(jù):", val)}()// 繼續(xù)發(fā)送數(shù)據(jù)time.Sleep(1 * time.Second)ch <- 3fmt.Println("發(fā)送了數(shù)據(jù) 3")// 接收數(shù)據(jù)time.Sleep(2 * time.Second)val := <-chfmt.Println("接收到的數(shù)據(jù):", val)
}
這里的 channel 有緩沖區(qū)大小為 2,因此前兩個(gè) ch <- 操作不會(huì)阻塞,因?yàn)榫彌_區(qū)有足夠空間。第三次發(fā)送數(shù)據(jù)時(shí),如果緩沖區(qū)已滿,發(fā)送方會(huì)阻塞,直到接收方讀取數(shù)據(jù)并釋放空間。
channel和select底層數(shù)據(jù)結(jié)構(gòu)是怎樣的?
Go 中 select 語句的底層實(shí)現(xiàn)涉及多個(gè)關(guān)鍵數(shù)據(jù)結(jié)構(gòu)和調(diào)度機(jī)制,主要是為了高效地處理通道(channel)操作和 Goroutine 調(diào)度。我們可以從 Go 語言的源代碼中窺探其底層數(shù)據(jù)結(jié)構(gòu)。以下是 select 相關(guān)的幾個(gè)重要底層數(shù)據(jù)結(jié)構(gòu)和其如何與通道和 Goroutine 協(xié)同工作:
- Goroutine 和 P 結(jié)構(gòu)
Go 的并發(fā)模型基于 Goroutine 和 M
調(diào)度模型。每個(gè) select 語句本質(zhì)上都會(huì)涉及到 Goroutine 的阻塞與喚醒。調(diào)度器的核心數(shù)據(jù)結(jié)構(gòu)包括:
Goroutine(G):代表一個(gè)執(zhí)行中的協(xié)程,每個(gè) Goroutine 都包含了當(dāng)前執(zhí)行狀態(tài)、棧信息等。當(dāng)某個(gè) select 語句阻塞 Goroutine 時(shí),Goroutine 會(huì)被掛起,并與通道關(guān)聯(lián)。
P(Processor):代表一個(gè)運(yùn)行 Goroutine 的處理器,它與 OS 線程(M)配合使用,管理并調(diào)度多個(gè) Goroutine。
當(dāng)某個(gè) select 語句涉及到通道的操作時(shí),如果通道未就緒,當(dāng)前 Goroutine 會(huì)被放入通道的等待隊(duì)列中,并掛起,直到被調(diào)度器喚醒。
- 通道(Channel)結(jié)構(gòu)
通道的底層結(jié)構(gòu)非常重要,因?yàn)?select 語句的核心在于處理通道操作。通道的內(nèi)部結(jié)構(gòu)如下
type hchan struct {qcount uint // 通道中已經(jīng)存在的數(shù)據(jù)個(gè)數(shù)dataqsiz uint // 環(huán)形隊(duì)列的大小buf unsafe.Pointer // 環(huán)形隊(duì)列的指針elemsize uint16 // 每個(gè)元素的大小closed uint32 // 通道是否關(guān)閉sendx uint // 發(fā)送操作的索引recvx uint // 接收操作的索引recvq waitq // 等待接收的 Goroutine 隊(duì)列sendq waitq // 等待發(fā)送的 Goroutine 隊(duì)列
}
recvq/sendq:表示接收和發(fā)送操作等待的 Goroutine 隊(duì)列。當(dāng) select 語句中有對通道的接收或發(fā)送操作時(shí),如果通道未就緒,當(dāng)前 Goroutine 會(huì)被加入相應(yīng)的等待隊(duì)列。
- SelectCase 結(jié)構(gòu)
Go 運(yùn)行時(shí)使用一個(gè)名為 SelectCase 的數(shù)據(jù)結(jié)構(gòu)來表示 select 語句中的每個(gè) case,每個(gè) SelectCase 代表一個(gè)通道操作。該結(jié)構(gòu)體中記錄了每個(gè) case 中的通道、操作類型(發(fā)送或接收)以及相關(guān)的數(shù)據(jù)指針等。
type scase struct {c *hchan // 指向通道的指針kind uint16 // 操作類型(發(fā)送、接收)pc uintptr // 程序計(jì)數(shù)器,用于跟蹤執(zhí)行位置elem unsafe.Pointer // 數(shù)據(jù)元素的指針,用于發(fā)送或接收操作
}
c:指向通道的指針,表示這個(gè) case 監(jiān)聽哪個(gè)通道。
kind:表示操作類型,是發(fā)送、接收還是默認(rèn) case。
elem:存儲(chǔ)數(shù)據(jù)的指針,用于發(fā)送或接收操作時(shí)的存取。
- Select 語句的執(zhí)行流程
當(dāng) Goroutine 執(zhí)行一個(gè) select 語句時(shí),Go 運(yùn)行時(shí)會(huì)執(zhí)行以下操作:
初始化 scase 列表:首先,select 語句會(huì)初始化每個(gè)通道操作,生成一個(gè) scase 列表來表示所有的 case。
檢測是否有就緒通道:然后,運(yùn)行時(shí)會(huì)遍歷這些 scase,檢測是否有通道已經(jīng)就緒(比如是否有數(shù)據(jù)可接收,或者通道是否可以發(fā)送數(shù)據(jù))。如果有通道就緒,立刻執(zhí)行相應(yīng)的操作,并返回。
阻塞等待:如果所有通道都未就緒,當(dāng)前 Goroutine 會(huì)掛起并加入到每個(gè)通道的等待隊(duì)列中。此時(shí),通道內(nèi)部的 recvq 或 sendq 隊(duì)列會(huì)保存當(dāng)前 Goroutine 的相關(guān)信息,當(dāng)通道狀態(tài)發(fā)生變化時(shí),這些隊(duì)列會(huì)被喚醒,調(diào)度器會(huì)重新調(diào)度等待的 Goroutine 。
隨機(jī)選擇通道:當(dāng)有多個(gè)通道同時(shí)就緒時(shí),Go 運(yùn)行時(shí)通過隨機(jī)函數(shù)來選擇一個(gè)通道執(zhí)行,保證公平性。- 調(diào)度器與 select
Go 調(diào)度器通過一組全局的隊(duì)列和局部隊(duì)列來管理 Goroutine 的運(yùn)行狀態(tài)。在 select 語句中,阻塞的 Goroutine 會(huì)被掛起到通道的等待隊(duì)列中,但它們?nèi)匀槐A粼谌只蚓植筷?duì)列中。當(dāng)通道狀態(tài)發(fā)生變化(如通道中有數(shù)據(jù)),調(diào)度器會(huì)從隊(duì)列中喚醒相關(guān)的 Goroutine 并將其重新加入執(zhí)行隊(duì)列。- select 的公平性和隨機(jī)性
Go 在 select 中實(shí)現(xiàn)了對多個(gè)通道操作的隨機(jī)選擇機(jī)制,避免某些通道操作被長期餓死。具體來說,當(dāng)有多個(gè)通道同時(shí)就緒時(shí),Go 會(huì)打亂 scase 列表的順序,并隨機(jī)選擇一個(gè)通道進(jìn)行處理。這確保了 select 的公平性,即使多個(gè) Goroutine 同時(shí)監(jiān)聽同一組通道,也不會(huì)導(dǎo)致某個(gè)通道長期得不到處理。
select 的核心數(shù)據(jù)結(jié)構(gòu)總結(jié)
Goroutine (G):Goroutine 是 select 語句中的執(zhí)行單元,當(dāng)一個(gè) select 阻塞時(shí),當(dāng)前 Goroutine 會(huì)掛起。
hchan:通道是核心數(shù)據(jù)結(jié)構(gòu),負(fù)責(zé)管理發(fā)送和接收操作,recvq 和 sendq 隊(duì)列保存了等待通道操作的 Goroutine。
scase:select 語句中每個(gè)通道操作的表示,存儲(chǔ)了通道的指針、操作類型等信息。
調(diào)度器:Go 調(diào)度器負(fù)責(zé)管理 Goroutine 的執(zhí)行和狀態(tài),當(dāng) select 語句涉及到阻塞操作時(shí),調(diào)度器會(huì)將 Goroutine 掛起并重新調(diào)度。
通過這些底層機(jī)制,Go 的 select 語句能夠高效地在并發(fā)場景下處理多個(gè)通道操作,并且在多個(gè)通道就緒時(shí)提供隨機(jī)選擇的公平性保障。