golang之channel

前言

本文算是对Diving Deep Into The Golang Channels的翻译,也算加强对channel的了解和使用。

使用channel

func goRoutineA(a <- chan int){
  val := <- a
  fmt.Println("goRoutineA received the data", val)
}

func main(){
  ch := make(chan int) // 定义一个channel:接收int类型
  go goRoutineA(ch)
  time.Sleep(time.Second * 1)  // 防止主线程退出看不到goroutine内容输出
}

整个执行流程如下


channel-1

channel-2

从上面两张图片看到:使用make(chan int)定义的channel,当channel中不存在数据时 在执行<- a时会被blocked直到channel中有数据。
在golang中使用channel能够使得runnable的goroutine在向channel发送或接收数据时处于blocked。

channel structure

在go中,channel实现了在不同的goroutine间传递message的基本。
当我们使用make函数来创建channel后,对应的结构应该是怎样的?

ch := make(chan int, 3)
在runtime时channel具体结构

接下来我们会针对其中的一些内容进行详解

hchan struct

当我们通过make(chan int, 3)创建一个buffer=3的channel时,就会创建一个hchan结构


hchan
  • dataqsize: 对应的channel的buffer大小,比如使用make(chan T, N),其中T代表channel中元素的类型,N就是channel的buffer大小;
  • elementsize:channel中的元素大小;
  • buf:channel中element真正存放的循环队列;不过该字段只有在使用buffered的channel时才有意义;
  • closed:记录当前channel是否已关闭,在使用make创建一个channel后,closed=0, 代表当前channel处于open;当调用close时可将该channel关闭,closed=1;代表当前channel不能再进行任何write操作。
  • recvq 和 sendq:都是等待队列,主要存放进行读取channel数据或写入channel数据时处于blocked的goroutines。
  • lock:主要用来保证channel的读写或发送接收是互斥操作。确保对channel的读取或写入的阻塞。

sudog struct

可将sudgo当成goroutine来理解


sudog结构

先将前面的实例进行调整下:

func goRoutineA(a <- chan int){
  val := <- a
  fmt.Println("goroutineA received the data", val)
}

func goRoutineB(b <- chan int){
  val := <- b
  fmt.Println("goroutineB received the data ", val)
}

func main(){
  ch := make(chan int)
  go goRoutineA(ch)
  go goRoutineB(ch)
   ch <- 3  
  time.Sleep(time.Second * 1)
}

对应的生成的channel结构如下:


channel结构

可以看到凸出部分展示了本实例中定义两个goroutine(goroutineA和goroutineB)来尝试读取channel中的数据。在执行 ch <- 3之前,由于channel中并没有任何数据,而两个goroutine将会阻塞在接收数据操作上,并用sudog进行包装,同时两个sudog会被存放到recvq里。
在channel中的recvq和sendq都是基于链表实现的,如下


channel之recvq

对于channel的sendq类似,此处不再累述。接下来看看当执行ch <-3发生了什么?

channel之send操作: c<- x

先看看如下几种send操作:

  • 1.对nil channel执行send操作
nil channel执行send

在对一个nil channel执行send操作时 会导致当前goroutine暂停其操作

  • 2.对closed channel执行send
closed channel

向一个已经closed的channel发送数据会触发一个panic

  • 3.当一个goroutine阻塞在channel上,send数据时会直接将数据发送该goroutine
blocked channel

该实例也说明recvq在其中扮演一个最终的角色:若是在recvq中任意一个goroutine在等待接收数据,对应的channel的wirter会直接将value传递给当前的goroutine(waiting receiver)。见send函数:


channel之send

在396行代码处 goready(gp, skip + 1),会使得在阻塞等待数据的那个goroutine将被再次runnable,go scheduler也将会再次运行该goroutine。

  • 4.Buffered Channel

当我们通过make(chan T, N)定义一个带有buffer的channel时,若是对应的hchan.buf还有可用空间则会将data存到到buffer中而不是像非buffered的channel一样处于阻塞,等待数据被接收。


buffered channel

chanbuf(c, i)直接访问相应的内存空间。
通过对比qcount和dataqsiz来判断hchan.buf是否还有free空间;通过将ep指针指向的区域copy到ringbuffer,来完成入列元素的send操作,并调整sendx和qcount。

  • 5.若是hchan.buf没有可用空间时 会如何???
full channel

上述代码:
首先会在当前stack上创建一个goroutine,并将该goroutine状态=park同时将该goroutine添加到sendq中。

关于send

1.将当前的channel进行blocked
2.确定执行write,会从recvq中获取一个等待的goroutine,并将对应的element直接写给该goroutine。
3.当对应的recvq是空的,首先要确保当前的buffer是否可用,若是可用,则从当前的goroutine的copy数据到buffer中
typedmemmove内部使用memmove将一个内存块从一个位置copy到另外一个位置。
4.若是buffer已满,则写入到channel的元素会被保存到当前运行的goroutine,并且当前goroutine将sendq处进行等待。
通过对比buffered channel和unbuffered channel差别在于对应的hchan分配有buffer。对于一个unbuffered channel 当send数据时并没有对应的receiver则会将元素保存到sudog中的elem字段,对应buffered channel也是同样的道理。
接下来会通过结合实例来阐述关于上面罗列的第4点:
如下代码只是用来演示 执行可能会导致一个panic

package main

func goroutineA(c2 chan int){
  c2 <- 2
}

func main(){
  c2 := make(chan int)
  go routineA(c2)

  for{}
}

如上的运行时channel的结构


unbuffered

不过即使我们将值2添加到channel中对应的buf却不存在该值,将会保存在goroutine的sudog结构中。在上面例子中goroutineA向channel c2发送数据,但此时并没有对应的receiver准备接收数据,因而goroutineA将被添加到channel的sendq列表中,并一直阻塞暂停等待receiver来获取数据。接下来看看运行时的sendq结构,来验证前面的内容


runtime sendq

这样在实例代码中 ch <- 2后具体发生的事宜。
而对于recvq来说如果存在等待状态的goroutine,它获取queue的第一个sudog并将数据放到goroutine中。

针对channel所有的transfer都是采用值copy的方式。也就是说在channel的所有的操作都是值拷贝。


值拷贝

正如上面演示样例 也是通过拷贝g的值到buffer中。
Don't communicate by sharing memory; share memory by communicating.

&{Ankur 25}
modifyUser Received Value &{Ankur Anand 100}
printUser goRoutine called &{Ankur 25}
&{Anand 100}
样例值拷贝

receive channel

其实跟channel send操作很类似。


channel receiver

Select: 多路复用

演示实例


multiplexing on multiple channel

1.在select代码块中的case执行都是互斥的,故而是需要select case中的channel来获取lock执行的,每个channel获取执行lock的顺序是基于Hchan地址的排序来进行lock的获取,这样就能确保不会同时锁定所有相关通道的互斥锁。

sellock(scases, lockorder)

每个在scases数组中的scase包括当前case的操作类型以及它所在的channel。


scase结构
  • kind 代表当前case的操作类型,可能取值:CaseRecv、CaseSend、CaseDefault
    2.计算轮询顺序:shuffle所有涉及的通道,以提供伪随机保证,并根据轮询顺序依次遍历所有情况,以查看其中是否有准备好进行通信。这个轮询顺序使得select操作不必遵循程序中声明的顺序。


    poll order

    case in select

    3.在select代码块中,只要有一个通道操作没有阻塞,select语句就可以返回,如果选择的通道已经准备好了,甚至不需要接触所有通道。
    若是当前没有通道响应,也没有默认语句,则当前g必须根据情况挂载所有通道的相应等待队列。
    若是当前所有的case都已准备好, 则会随机执行一个case。


    park goroutine in select case
  • sg.isSelect 代表goroutine正在参与当前的select块。

channel是go中一个非常强大和有趣的机制。但是为了有效地使用它们,你必须了解它们是如何工作的。希望本文能够解释Go中通道所涉及的非常基本的工作原理。

最后推荐Go Study Group 欢迎加入。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容