亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Golang并發(fā)繞不開的重要組件之Channel詳解

 更新時(shí)間:2023年06月04日 16:04:59   作者:Ted劉  
Channel是一個(gè)提供可接收和發(fā)送特定類型值的用于并發(fā)函數(shù)通信的數(shù)據(jù)類型,也是Golang并發(fā)繞不開的重要組件之一,本文就來和大家深入聊聊Channel的相關(guān)知識(shí)吧

在上一篇文章中有介紹Golang實(shí)現(xiàn)并發(fā)的重要關(guān)鍵字 go,通過這個(gè)我們可以方便快速地啟動(dòng)Goroutinue協(xié)程。協(xié)程之間一定會(huì)有通信的需求,而Golang的核心設(shè)計(jì)思想為:不通過共享內(nèi)存的方式進(jìn)行通信,而應(yīng)該通過通信來共享內(nèi)存。與其他通過共享內(nèi)存來進(jìn)行數(shù)據(jù)傳遞的編程語言略有差異,而實(shí)現(xiàn)這一方案的正是 Channel。

Channel是一個(gè)提供可接收和發(fā)送特定類型值的用于并發(fā)函數(shù)通信的數(shù)據(jù)類型,滿足FIFO(先進(jìn)先出)原則的隊(duì)列類型。FIFO在數(shù)據(jù)類型與操作上都有體現(xiàn):

  • Channel類型的元素是先進(jìn)先出的,先發(fā)送到Channel的元素會(huì)先被接收
  • 先向channel發(fā)送數(shù)據(jù)的Goroutinue會(huì)優(yōu)先執(zhí)行
  • 先從channel接收數(shù)據(jù)的Goroutinue會(huì)優(yōu)先執(zhí)行

Channel使用

語法

channel是Golang中的一種數(shù)據(jù)類型,相關(guān)語法也非常簡(jiǎn)單

ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType 

chan為channel類型關(guān)鍵字

<- 操作符用于channel中數(shù)據(jù)的收發(fā),在聲明時(shí)用于表示channel數(shù)據(jù)流動(dòng)的方向

  • chan 默認(rèn)為雙向傳遞,即channel既可以接收數(shù)據(jù)也可以發(fā)送數(shù)據(jù)
  • chan<- 僅可以發(fā)送數(shù)據(jù)的channel
  • <-chan 僅可以接受數(shù)據(jù)的channel

ElementType 代表元素類型,例如 int、string...

初始化

channel數(shù)據(jù)類型是一種引用類型,類似于map和slice,所以channel的初始化需要使用內(nèi)建函數(shù)make():

make(ChannelType, Capacity)

ch := make(chan int) 
var ch = make(chan int) 
ch := make(chan int, 10) 
ch := make(<-chan int) 
ch := make(chan<- int, 10)
  • ChannelType就是前面介紹的類型
  • Capacity代表緩沖容量。省略時(shí)就是為默認(rèn)0,表示無緩沖的Channel

如果不使用make()函數(shù)來初始化channel,則不能執(zhí)行收發(fā)通信操作,并且會(huì)造成阻塞,進(jìn)而造成Goroutinue泄露,示例:

func main() {
	defer func() {
		fmt.Println("goroutines: ", runtime.NumGoroutine())
	}()
	var ch chan int
	go func() {
		<-ch
	}()
	time.Sleep(time.Second)
}

代碼執(zhí)行結(jié)果為:

goroutines:  2

可以看到,直到程序退出,Goroutinue數(shù)量仍然為2,原因就是channel沒有正確的使用make()進(jìn)行初始化,channel變量實(shí)際為nil,進(jìn)而造成了內(nèi)存泄露。

數(shù)據(jù)的接收與發(fā)送

channel中數(shù)據(jù)的接收與發(fā)送是通過操作符 <- 來進(jìn)行操作的:

// 接收數(shù)據(jù)
ch <- Expression
ch <- 111
ch <- struct{}{}
// 發(fā)送數(shù)據(jù)
<- ch
v := <- ch
f(<-ch)

除了操作符 <- 外,我們還可以使用 for range 持續(xù)地從channel中接收數(shù)據(jù):

for e := range ch {
    // e逐個(gè)讀取ch中的元素值
}

持續(xù)接收操作與 <- 沒有很大區(qū)別:

  • 如果ch為nil channel就會(huì)阻塞
  • 如果ch沒有發(fā)送元素也會(huì)阻塞

for 會(huì)持續(xù)讀取直到channel執(zhí)行關(guān)閉,關(guān)閉后for會(huì)將剩余元素全部讀取之后結(jié)束。那么對(duì)已經(jīng)關(guān)閉的channel進(jìn)行數(shù)據(jù)的收發(fā)會(huì)怎樣呢?

channel的關(guān)閉

channel使用過后要使用內(nèi)置函數(shù)close()來關(guān)閉channel。關(guān)閉channel的意思是記錄該Channel不能再被發(fā)送任何元素了,而不是銷毀該Channel的意思。也就意味著關(guān)閉的Channel是可以繼續(xù)接收值的

  • 如果向已經(jīng)關(guān)閉的channel發(fā)送數(shù)據(jù)會(huì)引發(fā)panic
  • 關(guān)閉 nil channel 會(huì)引發(fā)panic
  • 關(guān)閉已經(jīng)關(guān)閉的 channel 會(huì)引發(fā)panic
  • 如果讀取已經(jīng)關(guān)閉的channel值,可以接收關(guān)閉前發(fā)送的全部值;關(guān)閉前的值接收完會(huì)返回類型的零值和一個(gè)false,不會(huì)阻塞及panic

以上幾種情況可以自己編寫一個(gè)簡(jiǎn)單的代碼來測(cè)試一下。

channel分類

前面有提到,在make一個(gè)channel時(shí),第二個(gè)參數(shù)就代表了緩沖區(qū)大小,如果沒有第二個(gè)參數(shù)就為默認(rèn)的無緩沖channel。具體用法:

  • 緩沖Channel,make(chan T, cap),cap是大于0的值。
  • 無緩沖Channel, make(chan T), make(chan T, 0)

無緩沖channel

無緩沖的channel也稱為同步Channel,只有當(dāng)發(fā)送方和接收方都準(zhǔn)備就緒時(shí),通信才會(huì)成功。

同步操作示例:

func ChannelSync() {
// 初始化數(shù)據(jù)
ch := make(chan int)
wg := sync.WaitGroup{}
    // 間隔發(fā)送
    wg.Add(1)
    go func() {
        defer wg.Done()
        for i := 0; i < 5; i++ {
            ch <- i
            println("Send ", i, ".\tNow:", time.Now().Format("15:04:05.999999999"))
            // 間隔時(shí)間
            time.Sleep(1 * time.Second)
        }
        close(ch)
    }()
    // 間隔接收
    wg.Add(1)
    go func() {
        defer wg.Done()
        for v := range ch {
            println("Received ", v, ".\tNow:", time.Now().Format("15:04:05.999999999"))
            // 間隔時(shí)間,注意與send的間隔時(shí)間不同
            time.Sleep(3 * time.Second)
        }
    }()
    wg.Wait()
}

執(zhí)行結(jié)果:

Send  0 .       Now: 17:54:27.772773
Received  0 .   Now: 17:54:27.772795
Received  1 .   Now: 17:54:30.773878
Send  1 .       Now: 17:54:30.773959
Received  2 .   Now: 17:54:33.775132
Send  2 .       Now: 17:54:33.775208
Received  3 .   Now: 17:54:36.775816
Send  3 .       Now: 17:54:36.775902
Received  4 .   Now: 17:54:39.776408
Send  4 .       Now: 17:54:39.776456

代碼中,采用同步channel,使用兩個(gè)goroutine完成發(fā)送和接收。每次發(fā)送和接收的時(shí)間間隔不同。我們分別打印發(fā)送和接收的值和時(shí)間。可以看到執(zhí)行結(jié)果:發(fā)送和接收時(shí)間一致;間隔以長(zhǎng)的為準(zhǔn),可見發(fā)送和接收操作為同步操作。因此,同步Channel適合在gotoutine間用做同步的信號(hào)

緩沖Channel

緩沖Channel也稱為異步Channel,接收和發(fā)送方不用等待雙方就緒即可成功。緩沖Channel會(huì)存在一個(gè)容量為cap的緩沖空間。當(dāng)使用緩沖Channel通信時(shí),接收和發(fā)送操作是在操作Channel的Buffer,是典型的隊(duì)列操作:

  • 接收時(shí),從緩沖中接收元素,只要緩沖不為空,不會(huì)阻塞。反之,緩沖為空,會(huì)阻塞,goroutine掛起
  • 發(fā)送時(shí),向緩沖中發(fā)送元素,只要緩沖未滿,不會(huì)阻塞。反之,緩沖滿了,會(huì)阻塞,goroutine掛起

操作示例:

func main() {
	// 初始化數(shù)據(jù)
	ch := make(chan int, 5)
	wg := sync.WaitGroup{}
	// 間隔發(fā)送
	wg.Add(1)
	go func() {
		defer wg.Done()
		for i := 0; i < 5; i++ {
			ch <- i
			println("Send ", i, ".\tNow:", time.Now().Format("15:04:05.999999999"))
			// 間隔時(shí)間
			time.Sleep(1 * time.Second)
		}
	}()
	// 間隔接收
	wg.Add(1)
	go func() {
		defer wg.Done()
		for v := range ch {
			println("Received ", v, ".\tNow:", time.Now().Format("15:04:05.999999999"))
			// 間隔時(shí)間,注意與send的間隔時(shí)間不同
			time.Sleep(3 * time.Second)
		}
	}()
	wg.Wait()
}

執(zhí)行結(jié)果:

Send  0 .       Now: 17:59:32.990698
Received  0 .   Now: 17:59:32.99071
Send  1 .       Now: 17:59:33.992127
Send  2 .       Now: 17:59:34.992832
Received  1 .   Now: 17:59:35.991488
Send  3 .       Now: 17:59:35.993155
Send  4 .       Now: 17:59:36.993445
Received  2 .   Now: 17:59:38.991663
Received  3 .   Now: 17:59:41.99184
Received  4 .   Now: 17:59:44.992214

代碼中,與同步channel一致,只是采用了容量為5的緩沖channel,使用兩個(gè)goroutine完成發(fā)送和接收。每次發(fā)送和接收的時(shí)間間隔不同。我們分別打印發(fā)送和接收的值和時(shí)間??梢钥吹綀?zhí)行結(jié)果:發(fā)送和接收時(shí)間不同;發(fā)送和接收操作不會(huì)阻塞,可見發(fā)送和接收操作為異步操作。因此,緩沖channel非常適合做goroutine之間的數(shù)據(jù)通信

Channel原理

源碼

在源碼包中的 runtime/chan.go 可以看到Channel實(shí)現(xiàn)源碼:

type hchan struct {
    qcount   uint           // 元素個(gè)數(shù),通過len()獲取
    dataqsiz uint           // 緩沖隊(duì)列的長(zhǎng)度,即容量,通過cap()獲取
    buf      unsafe.Pointer // 緩沖隊(duì)列指針,無緩沖隊(duì)列則為nil
    elemsize uint16         // 元素大小
    closed   uint32         // 關(guān)閉標(biāo)志
    elemtype \*\_type // 元素類型
    sendx    uint   // 發(fā)送元素索引
    recvx    uint   // 接收元素索引
    recvq    waitq  // 接收Goroutinue隊(duì)列
    sendq    waitq  // 發(fā)送Goroutinue隊(duì)列
        // lock protects all fields in hchan, as well as several
        // fields in sudogs blocked on this channel.
        //
        // Do not change another G's status while holding this lock
        // (in particular, do not ready a G), as this can deadlock
        // with stack shrinking.
    lock mutex // 鎖
}

buf 可以理解為一個(gè)環(huán)形數(shù)組,用來緩存Channel中的元素。為何使用環(huán)形數(shù)組而不使用普通數(shù)組呢?因?yàn)槠胀〝?shù)組更適合指定的空間,彈出元素時(shí),普通數(shù)組需要全部都前移,而使用環(huán)形數(shù)組+下標(biāo)索引的方式可以在不移動(dòng)元素的情況下實(shí)現(xiàn)數(shù)據(jù)的高效讀寫。

sendx與recvx 當(dāng)下標(biāo)超過數(shù)組容量后會(huì)回到第一個(gè)位置,所以需要有兩個(gè)字段記錄當(dāng)前讀和寫的下標(biāo)位置。

recvq與sendq 用于記錄等待接收和發(fā)送的goroutine隊(duì)列,當(dāng)基于某channel的接收或發(fā)送的goroutine無法理解執(zhí)行時(shí),也就是需要阻塞時(shí),會(huì)被記錄到Channel的等待隊(duì)列中。當(dāng)channel可以完成相應(yīng)的接收或發(fā)送操作時(shí),從等待隊(duì)列中喚醒goroutine進(jìn)行操作。

等待隊(duì)列實(shí)際是一個(gè)雙向鏈表結(jié)構(gòu)

生命周期

創(chuàng)建策略

  • 無緩沖的直接分配內(nèi)存
  • 有緩沖的不包含指針,為hchan和底層數(shù)組分配連續(xù)的地址
  • 有緩沖的channel且包含元素指針,會(huì)為hchan和底層數(shù)組分配地址

發(fā)送策略

  • 發(fā)送操作編譯時(shí)轉(zhuǎn)換為 runtime.chansend函數(shù)
  • 阻塞式:block=true;非阻塞式:block=false
  • 向channel中發(fā)送數(shù)據(jù)分為檢查和數(shù)據(jù)發(fā)送兩塊,數(shù)據(jù)發(fā)送:
    • 如果channel的讀等待隊(duì)列存在接受者goroutinue
      • 將數(shù)據(jù)直接發(fā)送給第一個(gè)等待的goroutinue,喚醒接收的goroutinue
    • 如果channel讀等待隊(duì)列不存在接收者goroutinue
      • 如果循環(huán)數(shù)組buf未滿,則將數(shù)據(jù)發(fā)送到循環(huán)數(shù)組buf的隊(duì)尾
      • 如果循環(huán)數(shù)組buf已滿,這時(shí)就會(huì)走阻塞發(fā)送的流程,將當(dāng)前goroutinue加入寫等待隊(duì)列,并掛起等待喚醒

接收策略

  • 接收操作編譯是轉(zhuǎn)換為 runtime.chanrecv 函數(shù)
  • 阻塞式:block=true;非阻塞式:block=false
  • 向channel中接收數(shù)據(jù)數(shù)據(jù)接收:
    • 如果channel的寫等待隊(duì)列存在發(fā)送者goroutinue:
      • 如果是無緩沖channel,直接從第一個(gè)發(fā)送者goroutinue將數(shù)據(jù)拷貝給接收變量,喚醒發(fā)送的goroutinue
      • 如果是有緩沖channel(已滿),將循環(huán)數(shù)組buf的隊(duì)首元素拷貝給接收變量,將第一個(gè)發(fā)送者goroutinue的數(shù)據(jù)拷貝到buf循環(huán)數(shù)組,喚醒發(fā)送的goroutinue
    • 如果channel的寫等待不存在發(fā)送者goroutinue
      • 如果循環(huán)數(shù)組buf非空,將循環(huán)數(shù)組buf的隊(duì)首元素拷貝給接收變量
      • 如果循環(huán)數(shù)組buf為空,這個(gè)時(shí)候就會(huì)走阻塞接收的流程,將當(dāng)前 goroutine 加入讀等待隊(duì)列,并掛起等待喚醒

關(guān)閉

調(diào)用 runtime.closechan 函數(shù)

簡(jiǎn)單的對(duì)Channel一些基礎(chǔ)用法及原理做了一個(gè)解釋,可以多寫一寫并發(fā)代碼以及閱讀源碼來加深對(duì)Channel的理解。

以上就是Golang并發(fā)繞不開的重要組件之Channel詳解的詳細(xì)內(nèi)容,更多關(guān)于Golang Channel的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 解決Goland 提示 Unresolved reference 錯(cuò)誤的問題

    解決Goland 提示 Unresolved reference 錯(cuò)誤的問題

    這篇文章主要介紹了解決Goland 提示 Unresolved reference 錯(cuò)誤的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Golang的md5 hash計(jì)算操作

    Golang的md5 hash計(jì)算操作

    這篇文章主要介紹了Golang的md5 hash計(jì)算操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Go json反序列化“null“的問題解決

    Go json反序列化“null“的問題解決

    本文主要介紹了Go json反序列化“null“的問題解決,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • 深入淺出Go:掌握基礎(chǔ)知識(shí)的關(guān)鍵要點(diǎn)

    深入淺出Go:掌握基礎(chǔ)知識(shí)的關(guān)鍵要點(diǎn)

    Go是一種開源的編程語言,由Google開發(fā),它具有簡(jiǎn)潔、高效、并發(fā)性強(qiáng)的特點(diǎn),適用于構(gòu)建可靠的、高性能的軟件系統(tǒng),本文將介紹Go的基礎(chǔ)知識(shí),需要的朋友可以參考下
    2023-10-10
  • Golang嵌入資源文件實(shí)現(xiàn)步驟詳解

    Golang嵌入資源文件實(shí)現(xiàn)步驟詳解

    在應(yīng)用程序中附帶代碼以外的其他資源可能會(huì)很有用,常用的實(shí)現(xiàn)方法是嵌入對(duì)象或數(shù)據(jù)。在數(shù)據(jù)庫中存儲(chǔ)數(shù)據(jù)應(yīng)用中,需要定義schema,在應(yīng)用啟動(dòng)時(shí)創(chuàng)建表,但如果找不到schema文件呢?Go1.16提供embed包讓實(shí)現(xiàn)變得簡(jiǎn)單,之前很多第三方包實(shí)現(xiàn)類似功能
    2023-01-01
  • GO語言實(shí)現(xiàn)文件上傳代碼分享

    GO語言實(shí)現(xiàn)文件上傳代碼分享

    本文給大家分享的是一則使用golang實(shí)現(xiàn)文件上傳的代碼,主要是使用os.Create創(chuàng)建文件,io.Copy來保存文件,思路非常清晰,這里推薦給大家,有需要的小伙伴參考下吧。
    2015-03-03
  • Go uuid庫的具體使用

    Go uuid庫的具體使用

    在現(xiàn)代軟件開發(fā)中,全球唯一標(biāo)識(shí)符(UUID)在許多場(chǎng)景中發(fā)揮著重要的作用,本文主要介紹了Go uuid庫的具體使用,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-11-11
  • 使用goland調(diào)試遠(yuǎn)程代碼的操作步驟

    使用goland調(diào)試遠(yuǎn)程代碼的操作步驟

    大家都知道如何在goland調(diào)試遠(yuǎn)程代碼嗎?今天小編給大家分享一篇教程幫助大家學(xué)習(xí)goland調(diào)試遠(yuǎn)程代碼的操作步驟,感興趣的朋友跟隨小編一起看看吧
    2021-06-06
  • Go語言處理超大字符串型整數(shù)加減經(jīng)典面試詳解

    Go語言處理超大字符串型整數(shù)加減經(jīng)典面試詳解

    這篇文章主要為大家介紹了Go語言處理超大字符串型整數(shù)加減經(jīng)典面試示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • Go語言學(xué)習(xí)之文件操作方法詳解

    Go語言學(xué)習(xí)之文件操作方法詳解

    這篇文章主要為大家詳細(xì)介紹了Go語言中一些常見的文件操作,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Go語言有一定的幫助,需要的可以參考一下
    2022-04-04

最新評(píng)論