Golang channel 之 写操作 send

上一篇:Golang channel 之 数据结构
下一篇:Golang channel 之 读操作 recv

channel的常规写操作

假如有一个元素类型为int的channel,变量名为ch,那么常规的写操作(简称send为写)在代码中的写法如下所示:

ch <- 10

其中ch可能是“有缓冲”的,也可能是“无缓冲”的,甚至可能为nil。

按照上面的写法,有两种情况能使写操作不会阻塞:
1)通道ch的recvq里已有goroutine在等待;
2)通道ch是“有缓冲”的,且缓冲区没有用尽。

第一种情况中,只要ch的recvq里有协程在排队,当前协程就直接把数据交给recvq队首的那个协程就好了,然后两个协程都可以继续执行,无关ch有没有缓冲;

第二种情况中,ch是有缓冲的,且缓冲区没有用尽,也就是底层数组没有存满,那么当前协程直接把数据追加到缓冲数组中,就可以继续执行。

同样是上面的写法,有三种情况会使写操作阻塞:
1)通道ch为nil;
2)通道ch无缓冲且recvq为空;
3)通道ch有缓冲且缓冲区已用尽。

第一种情况中,参照golang的实现,允许对nil通道执行写操作,但是会使当前协程永久性的阻塞在这个nil通道上,例如如下代码会因死锁抛出异常:

package main

func main() {
        var ch chan int
        ch <- 10
}

第二种情况中,ch为无缓冲通道,recvq中没有协程在等待,所以当前协程需要到通道的sendq中排队;

第三种情况中,ch有缓冲且已用尽,隐含的信息就是recvq为空,否则缓冲不会用尽,所以当前协程只能到sendq中排队。

channel的非阻塞写操作

熟悉并发编程的同学应该知道,有些锁是支持tryLock操作的,也就是我想获得这把锁,但是万一已经被别人获得了,我不阻塞等待,可以去干其他事情。

对于通道的非阻塞写就是:我想向通道写数据,但是如果当前没有读者在排队等待,且缓冲区没有剩余空间(包含无缓冲),我就需要阻塞等待。但是我不想等待,所以立刻返回并告诉我“现在不能写”就可以了。

在golang中,对于单个通道的非阻塞写操作可以用如下代码实现,注意是一个select、一个case和一个default,都是一个,不能多也不能少:

select {
case ch <- 10:
    ...
default:
    ...
}

如果检测到写ch不会阻塞,那么就会执行case ch <- 10:分支,如果会阻塞,就会执行default:分支。关于什么情况下会阻塞,什么情况下不会阻塞,参见上面的情况分析。

channel写操作的实现

上面简单的分析了channel的常规写操作和非阻塞写操作,虽然两者在形式上看起来稍微有些差异,但是主要逻辑都是通过runtime.chansend函数实现的,下面简单的进行一下解读:

首先来看一下chansend函数的原型:

func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool

其中:
c是一个hchan指针,指向要用来send数据的channel;
ep是一个指针,指向要被写入通道c的数据,数据类型要和c的元素类型一致;
block表示如果写操作不能立即完成,是否想要阻塞等待;
callerpc用以进行race相关检测,暂时不需要关心;
返回值为true表示数据写入完成,false表示目前不可写但因为不想阻塞(block为false)而返回。

chansend函数的主要逻辑如下:

如果c等于nil {
    如果不想阻塞 {
        return false
    }
    永久阻塞
}

如果(不想阻塞 且 c未关闭) 且 ((c无缓冲 且 recvq是空的) 或 (c有缓冲 且 缓冲区已满)) {
    return false
}

对c加锁

如果c已关闭 {
    解锁c
    panic("send on closed channel")
}

如果recvq中有内容 {
    取出队首协程,并把数据传递给它并解锁c
    return true
}

如果缓冲区还有空间 {
    把数据追加到缓冲区,移动sendx,递增qcount
    解锁c
    return true
}

如果不想阻塞 {
    解锁c
    return false
}

进入sendq排队等待同时解锁c,条件满足时会完成数据传递并被唤醒

return true

逐块对应以上代码:
1)如果c为nil,进一步判断block:如果block为false,那么直接返回false,表示未send数据;如果block为true,那么就让当前协程”永久“的阻塞在这个nil通道上;
2)如果block为false且closed为0,也就是在”不想阻塞“且通道”未关闭“的前提下,如果是通道”无缓冲“且recvq为空,或者是通道”有缓冲“且缓冲已用尽,则直接返回false。本步判断是在不加锁的情况下进行的,目的是让非阻塞写在无法立即完成时能真正不阻塞(加锁可能阻塞)。此处有疑问的话,可返回上面看常规写操作的情况分析;
3)加锁;
4)如果closed不为0,即通道已经关闭的话,则解锁,然后panic。因为不可以写入已关闭的通道;
5)如果recvq不为空,就从中取出第一个排队的协程,将数据传递给这个协程,并将该协程置为ready状态(放入run queue,进而得到调度),然后解锁,返回true;
6)通过比较qcount和dataqsiz判断缓冲区是否还有剩余空间,在这里”无缓冲“的通道被视为没有剩余空间。有剩余空间的话,将数据追加到缓冲区中,移动sendx,增加qcount,解锁,返回true;
7)如果block为false,即不想阻塞,则解锁,返回false;
8)最后,到达这里就是阻塞写了,当前协程把自己追加到通道的sendq中阻塞排队,同时解锁,等到条件满足时会被唤醒。

流程比较长,还是画个图吧:


chansend.png

本篇总结

1)channel的常规写操作如c <- x,会被编译器转换为对runtime.chansend1的调用,后者内部只是调用了runtime.chansend;
2)非阻塞式的写操作,即select、case、default三个一,会被编译器转换为对runtime.selectnbsend的调用,后者也仅仅是调用了runtime.chansend。非阻塞写的实现效果如下:

select {
case c <- v:
    ... foo
default:
    ... bar
}

// 被编译器转化为:

if selectnbsend(c, v) {
    ... foo
} else {
    ... bar
}

上一篇:Golang channel 之 数据结构
下一篇:Golang channel 之 读操作 recv

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

推荐阅读更多精彩内容