最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

深入理解golang chan的使用

 更新時(shí)間:2022年06月02日 09:30:54   作者:煙草的香味  
本文主要介紹了golang chan的使用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

前言

之前在看golang多線程通信的時(shí)候, 看到了go 的管道. 當(dāng)時(shí)就覺得這玩意很神奇, 因?yàn)橹敖佑|過的不管是php, java, Python, js, c等等, 都沒有這玩意, 第一次見面, 難免勾起我的好奇心. 所以就想著看一看它具體是什么東西. 很明顯, 管道是go實(shí)現(xiàn)在語言層面的功能, 所以我以為需要去翻他的源碼了. 雖然最終沒有翻到C的層次, 不過還是受益匪淺.

見真身

結(jié)構(gòu)體

要想知道他是什么東西, 沒什么比直接看他的定義更加直接的了. 但是其定義在哪里么? 去哪里找呢? 還記得我們是如何創(chuàng)建chan的么? make方法. 但是當(dāng)我找過去的時(shí)候, 發(fā)現(xiàn)make方法只是一個(gè)函數(shù)的聲明.

這, 還是沒有函數(shù)的具體實(shí)現(xiàn)啊. 匯編看一下. 編寫以下內(nèi)容:

package main

func main() {
	_ = make(chan int)
}

執(zhí)行命令:

go tool compile -N -l -S main.go

雖然匯編咱看不懂, 但是其中有一行還是引起了我的注意.

make調(diào)用了runtime.makechan. 漂亮, 就找他.

找到他了, 是hchan指針對(duì)象. 整理了一下對(duì)象的字段(不過人家自己也有注釋的):

// 其內(nèi)部維護(hù)了一個(gè)循環(huán)隊(duì)列(數(shù)組), 用于管理發(fā)送與接收的緩存數(shù)據(jù). 
type hchan struct {
  // 隊(duì)列中元素個(gè)數(shù)
	qcount   uint
  // 隊(duì)列的大小(數(shù)組長(zhǎng)度)
	dataqsiz uint
  // 指向底層的緩存隊(duì)列, 是一個(gè)可以指向任意類型的指針. 
	buf      unsafe.Pointer
  // 管道每個(gè)元素的大小
	elemsize uint16
  // 是否被關(guān)閉了
	closed   uint32
  // 管道的元素類型
	elemtype *_type
  // 當(dāng)前可以發(fā)送的元素索引(隊(duì)尾)
	sendx    uint  
  // 當(dāng)前可以接收的元素索引(隊(duì)首)
	recvx    uint  
  // 當(dāng)前等待接收數(shù)據(jù)的 goroutine 隊(duì)列
	recvq    waitq
  // 當(dāng)前等待發(fā)送數(shù)據(jù)的 goroutine 隊(duì)列
	sendq    waitq 
	// 鎖, 用來保證管道的每個(gè)操作都是原子性的. 
	lock mutex
}

可以看的出來, 管道簡(jiǎn)單說就是一個(gè)隊(duì)列加一把鎖.

發(fā)送數(shù)據(jù)

依舊使用剛才的方法分析, 發(fā)送數(shù)據(jù)時(shí)調(diào)用了runtime.chansend1 函數(shù). 其實(shí)現(xiàn)簡(jiǎn)單易懂:

然后查看真正實(shí)現(xiàn), 函數(shù)步驟如下(個(gè)人理解, 有一些 test 使用的代碼被我刪掉了. ):

func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
  // 異常處理, 若管道指針為空
	if c == nil {
		if !block {
			return false
		}
		gopark(nil, nil, waitReasonChanSendNilChan, traceEvGoStop, 2)
		throw("unreachable")
	}
	// 常量判斷, 恒為 false, 應(yīng)該是開發(fā)時(shí)調(diào)試用的. 
	if debugChan {
		print("chansend: chan=", c, "\n")
	}
	// 常量, 恒為 false, 沒看懂這個(gè)判斷
	if raceenabled {
		racereadpc(c.raceaddr(), callerpc, funcPC(chansend))
	}
  // 若當(dāng)前操作不阻塞, 且管道還沒有關(guān)閉時(shí)判斷
  // 當(dāng)前隊(duì)列容量為0且沒有等待接收數(shù)據(jù)的 或 當(dāng)前隊(duì)列容量不為0且隊(duì)列已滿
  // 那么問題來了, 什么時(shí)候不加鎖呢? select 的時(shí)候. 可以在不阻塞的時(shí)候快速返回
	if !block && c.closed == 0 && ((c.dataqsiz == 0 && c.recvq.first == nil) ||
		(c.dataqsiz > 0 && c.qcount == c.dataqsiz)) {
		return false
	}
	// 上鎖, 保證操作的原子性
	lock(&c.lock)
	// 若管道已經(jīng)關(guān)閉, 報(bào)錯(cuò)
	if c.closed != 0 {
		unlock(&c.lock)
		panic(plainError("send on closed channel"))
	}
	// 從接受者隊(duì)列獲取一個(gè)接受者, 若存在, 數(shù)據(jù)直接發(fā)送, 不走緩存, 提高效率
	if sg := c.recvq.dequeue(); sg != nil {
		send(c, sg, ep, func() { unlock(&c.lock) }, 3)
		return true
	}
	// 若緩存為滿, 則將數(shù)據(jù)放到緩存中排隊(duì)
	if c.qcount < c.dataqsiz {
    // 取出對(duì)尾的地址
		qp := chanbuf(c, c.sendx)
    // 將ep 的內(nèi)容拷貝到 ap 地址
		typedmemmove(c.elemtype, qp, ep)
    // 更新隊(duì)尾索引
		c.sendx++
		if c.sendx == c.dataqsiz {
			c.sendx = 0
		}
		c.qcount++
		unlock(&c.lock)
		return true
	}
	// 若當(dāng)前不阻塞, 直接返回
	if !block {
		unlock(&c.lock)
		return false
	}
	// 當(dāng)走到這里, 說明數(shù)據(jù)沒有成功發(fā)送, 且需要阻塞等待. 
  // 以下代碼沒看懂, 不過可以肯定的是, 其操作為阻塞當(dāng)前協(xié)程, 等待發(fā)送數(shù)據(jù)
	gp := getg()
	mysg := acquireSudog()
	mysg.releasetime = 0
	if t0 != 0 {
		mysg.releasetime = -1
	}
	mysg.elem = ep
	mysg.waitlink = nil
	mysg.g = gp
	mysg.isSelect = false
	mysg.c = c
	gp.waiting = mysg
	gp.param = nil
	c.sendq.enqueue(mysg)
	gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanSend, traceEvGoBlockSend, 2)
	KeepAlive(ep)
	if mysg != gp.waiting {
		throw("G waiting list is corrupted")
	}
	gp.waiting = nil
	gp.activeStackChans = false
	if gp.param == nil {
		if c.closed == 0 {
			throw("chansend: spurious wakeup")
		}
		panic(plainError("send on closed channel"))
	}
	gp.param = nil
	if mysg.releasetime > 0 {
		blockevent(mysg.releasetime-t0, 2)
	}
	mysg.c = nil
	releaseSudog(mysg)
	return true
}

雖然最終阻塞的地方?jīng)]看太明白, 不過發(fā)送數(shù)據(jù)的大體流程很清楚:

  • 若無需阻塞且不能發(fā)送數(shù)據(jù), 返回失敗
  • 若存在接收者, 直接發(fā)送數(shù)據(jù)
  • 若存在緩存, 將數(shù)據(jù)放到緩存中
  • 若無需阻塞, 返回失敗
  • 阻塞等待發(fā)送數(shù)據(jù)

其中不加鎖的操作, 在看到selectnbsend函數(shù)的注釋時(shí)如下:

// compiler implements
//
//	select {
//	case c <- v:
//		... foo
//	default:
//		... bar
//	}
//
// as
//
//	if selectnbsend(c, v) {
//		... foo
//	} else {
//		... bar
//	}
//
func selectnbsend(c *hchan, elem unsafe.Pointer) (selected bool) {
	return chansend(c, elem, false, getcallerpc())
}

看這意思, select關(guān)鍵字有點(diǎn)類似于語法糖, 其內(nèi)部會(huì)轉(zhuǎn)換成調(diào)用selectnbsend函數(shù)的簡(jiǎn)單if判斷.

接收數(shù)據(jù)

至于接收數(shù)據(jù)的方法, 其內(nèi)部實(shí)現(xiàn)與發(fā)送大同小異. runtime.chanrecv 方法.

源碼簡(jiǎn)單看了一下, 雖理解不深, 但對(duì)channel也有了大體的認(rèn)識(shí).

上手

簡(jiǎn)單對(duì)channel的使用總結(jié)一下.

定義

// 創(chuàng)建普通的管道類型, 非緩沖
a := make(chan int)
// 創(chuàng)建緩沖區(qū)大小為10的管道
b := make(chan int, 10)
// 創(chuàng)建只用來發(fā)送的管道
c := make(chan<- int)
// 創(chuàng)建只用來接收的管道
d := make(<-chan int)
// eg: 只用來接收的管道, 每秒一個(gè)
e := time.After(time.Second)

發(fā)送與接收

// 接收數(shù)據(jù)
a := <- ch
b, ok := <- ch
// 發(fā)送數(shù)據(jù)
ch <- 2

最后, 看了一圈, 感覺channel并不是很復(fù)雜, 就是一個(gè)隊(duì)列, 一端接受, 一端發(fā)送. 不過其對(duì)多協(xié)程處理做了很多優(yōu)化. 與協(xié)程配合, 靈活使用的話, 應(yīng)該會(huì)有不錯(cuò)的效果.

到此這篇關(guān)于深入理解golang chan的使用的文章就介紹到這了,更多相關(guān)golang chan內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 以alpine作為基礎(chǔ)鏡像構(gòu)建Golang可執(zhí)行程序操作

    以alpine作為基礎(chǔ)鏡像構(gòu)建Golang可執(zhí)行程序操作

    這篇文章主要介紹了以alpine作為基礎(chǔ)鏡像構(gòu)建Golang可執(zhí)行程序操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • go benchmark 基準(zhǔn)測(cè)試詳解

    go benchmark 基準(zhǔn)測(cè)試詳解

    這篇文章主要介紹了go benchmark 基準(zhǔn)測(cè)試詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-03-03
  • Go動(dòng)態(tài)調(diào)用函數(shù)的實(shí)例教程

    Go動(dòng)態(tài)調(diào)用函數(shù)的實(shí)例教程

    本文主要介紹了Go動(dòng)態(tài)調(diào)用函數(shù)的實(shí)例教程,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-01-01
  • go時(shí)間/時(shí)間戳操作大全(小結(jié))

    go時(shí)間/時(shí)間戳操作大全(小結(jié))

    這篇文章主要介紹了go時(shí)間/時(shí)間戳操作大全,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-07-07
  • Go語言os包用法詳解

    Go語言os包用法詳解

    本文主要介紹了Go語言os包用法詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • Go標(biāo)準(zhǔn)容器之Ring的使用說明

    Go標(biāo)準(zhǔn)容器之Ring的使用說明

    這篇文章主要介紹了Go標(biāo)準(zhǔn)容器之Ring的使用說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • golang開發(fā)?gorilla?websocket的使用示例詳解

    golang開發(fā)?gorilla?websocket的使用示例詳解

    這篇文章主要介紹了golang開發(fā)?gorilla?websocket的使用示例詳解,介紹了websocket的簡(jiǎn)單使用,我們使用的版本是1.3.0,具體操作方法跟隨小編一起學(xué)習(xí)吧
    2024-05-05
  • Go Gin框架中的binding驗(yàn)證器使用小結(jié)

    Go Gin框架中的binding驗(yàn)證器使用小結(jié)

    Gin框架中的binding驗(yàn)證器為我們提供了簡(jiǎn)便的數(shù)據(jù)綁定和驗(yàn)證功能,通過合理使用binding和validate標(biāo)簽,我們可以確保API接口的數(shù)據(jù)合法性和完整性,這篇文章主要介紹了Go Gin框架中的binding驗(yàn)證器使用指南,需要的朋友可以參考下
    2024-07-07
  • 一文帶你了解Go語言中接口的使用

    一文帶你了解Go語言中接口的使用

    這篇文章主要和大家分享一下Go語言中的接口的使用,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Go語言有一定的幫助,需要的小伙伴可以參考一下
    2022-12-12
  • GoFrame框架gredis優(yōu)雅的取值和類型轉(zhuǎn)換

    GoFrame框架gredis優(yōu)雅的取值和類型轉(zhuǎn)換

    這篇文章主要為大家介紹了GoFrame框架gredis優(yōu)雅的取值和類型轉(zhuǎn)換,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06

最新評(píng)論

武穴市| 望谟县| 绩溪县| 克东县| 搜索| 重庆市| 都兰县| 阜阳市| 达孜县| 榆树市| 连州市| 城市| 江北区| 南木林县| 乐都县| 孟州市| 夹江县| 盐边县| 满城县| 阿拉善左旗| 黑水县| 皋兰县| 敦煌市| 项城市| 福安市| 闵行区| 阜康市| 富川| 镇康县| 长沙县| 确山县| 安陆市| 海兴县| 北碚区| 镶黄旗| 嘉黎县| 报价| 韶关市| 神木县| 新丰县| 甘洛县|