范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

golang各种channel操作的底层实现

  首先,未初始化的channel变量值为nil:
  channel底层其实就是个指针,这个下面会讲,所以其nil值,在底层就是用0表示的,如上面的输出。
  上图是main函数的汇编,其中选中的两行,就是调用p函数的逻辑。
  p函数的参数是通过ax寄存器传递的,由上图可见,在调用p函数之前,ax寄存器的值,通过xorl指令进行了清零,这也是channel变量的nil值,在底层是用0表示的另一个佐证。
  channel是通过make函数创建的,make可以创建unbuffered channel,也可以创建buffered channel:
  make其实并不是一个真正的函数,它会在编译阶段,被go编译器替换为对runtime.makechan函数的调用:
  该替换信息也可以通过汇编查看:
  上图中选中行为make函数的汇编逻辑,其中hello.go:8的第四行,和hello.go:11的第三行,都是在调用runtime.makechan函数。
  在看runtime.makechan函数之前,我们先通过上图的汇编,看下make创建unbuffered channel和buffered channel的方式有什么不同。
  源码中第8行是创建unbuffered channel,对应到汇编里,就是上图中hello.go:8的那4行。
  源码中第11行是创建buffered channel,对应到汇编里,就是上图中hello.go:11的那4行
  由上图可见,hello.go:8和hello.go:11的逻辑基本相同,都是先将要创建channel的类型,放到ax寄存器里,然后将要创建channel的buffer size,放到bx寄存器里,最后调用runtime.makechan函数,转入真正的创建channel逻辑。
  这里需要说明一下,go函数之间的调用,其参数和结果的传递,主要是通过寄存器来完成的(go1.17之后,之前是通过栈),对于上面的makechan函数来说,第一个参数是通过ax寄存器传递的,第二个参数是通过bx寄存器传递的。
  有关go函数之间调用,参数和结果传递方式的具体规则,请参考以下文档:
  https://github.com/golang/go/blob/master/src/cmd/compile/abi-internal.md
  再回到上面的问题,由上图汇编可见,创建unbuffered channel和buffered channel的流程是一样的,唯一区别就是bx寄存器的值不同,即指定的buffer size不一样。
  hello.go:8是创建unbuffered channel,bx的值是0,hello.go:11是创建buffered channel,bx的值是8,即我们在源码中指定的值。
  所以,make(chan int)其实等价于make(chan int, 0),即buffer size等于0。
  接下来我们看一下runtime.makechan函数的实现:
  连接上文,参数t,即channel的类型,是通过ax寄存器传递的,参数size,即channel的buffer size,是通过bx寄存器传递的。
  上面我们也提到,channel变量底层其实就是个指针,该指针的类型,就是上图中makechan函数的返回类型*hchan:
  hchan结构体各字段的用途是:
  qcount表示的是当前channel的buffer里已经缓冲了几个元素。如果是unbuffered channel,该字段一直为0。
  dataqsiz表示的是当前channel的buffer里最多可缓冲几个元素,即上文我们提到的buffer size。如果是unbuffered channel,该字段一直为0。
  buf指向的是用于接受缓冲元素的总内存,我们可以把它理解成一个数组,数组的元素类型就是channel的元素类型,数组的最大容量,就是上面dataqsiz的值。如果是unbuffered channel,该buf不用分配。
  elemsize表示的是channel元素类型的大小。
  closed表示的是当前channel是否已关闭,即调用过close(c)方法。
  elemtype表示的是channel的元素类型。
  sendx表示的是下一次向channel中发送数据,该数据会被拷贝到buf字段表示的元素数组的位置,当该位置超过数组最大值以后,会从0重新开始。如果是unbuffered channel,该字段一直为0。
  recvx表示的是下一次从channel中接收数据,该接收会从buf字段表示数组的recvx位置拷贝数据到目标内存。如果是unbuffered channel,该字段一直为0。
  recvq表示的是等待从channel中接受数据的goroutine队列。当buf中缓冲的元素个数为0,且sendq表示的等待发送数据的goroutine队列为空时,再有goroutine想从这个channel中读取数据,就会被阻塞等待在这里队列里。
  sendq表示的是等待向channel中发送数据的goroutine队列。当buf中缓冲的元素个数已达到最大值,且recvq表示的等待接受数据的goroutine队列为空时,再有goroutine想向这个channel里发送数据,就会被阻塞等待在这个队列里。
  lock表示的是channel的锁,为了保证channel读写的并发安全,channel的很多操作都是加锁的。
  有关以上字段是如何配合使用的,这个会在下文的各种示例分析中看到。
  这里有一点需要注意的是,因为channel的元素传递是拷贝操作,所以如果channel元素类型占较大内存,要考虑是否应该传递其指针。
  上面makechan的那张图中,83到85行是调试信息,如果debugChan为true,则在创建channel时,会输出channel的元素大小,及可缓冲的元素个数等信息。
  下面我们修改下debugChan的值,然后写个例子看下其输出。
  示例代码:
  go build构建程序并执行:
  上图选中行中,第一行是make(chan S)的输出,该make创建的channel,元素大小为80字节,可缓冲元素个数为0。
  第二行是make(chan S, 8)的输出,它创建的channel,元素大小也为80字节,可缓冲元素个数为8。
  因为channel元素传递是拷贝操作,所以对于这个示例来说,每次send或receive时,都要拷贝80字节,此种情况下,就应该考虑将该channel改为传递S的指针,而不是S本身。
  接下来看下如何向channel中发送数据:
  也是在编译阶段,c <- v被转成了对runtime.chansend1函数的调用:
  同样,我们也可以根据汇编代码得出该信息:
  上图是send函数的汇编逻辑,其中和发送相关的,是上图中的选中行,即hello.go:10的那4行。
  根据go的calling convention可知,上面示例中的send函数在被调用时,参数c被放到了ax寄存器里,参数v被放到了bx寄存器里。
  再看上图中的汇编代码,hello.go:10中的第一行把bx的值,放到了栈的0x10(SP)位置,接着把该位置的地址,又放到了bx里,也就是说,此时bx里存放的是参数v的地址。
  接着在hello.go:10中的第四行,调用runtime.chansend1函数,该函数的两个参数,代表channel的变量c,以及要发送的值v的地址,同样也是通过ax和bx传递过去。
  来看下runtime.chansend1函数:
  该函数的参数c,就是上面示例中send函数的参数c,该函数的参数elem,就是上面示例中send函数参数v的地址。
  该函数又调用了chansend函数:
  chansend函数是向channel发送数据的主体逻辑,其大致步骤请参考上图中的注释,同时也可以结合上文提到的,hchan结构体中各字段的意义,来理解这段代码。
  由上图可见,为了保证逻辑的正确性,向channel发送数据的操作都进行了加锁,所以,虽然channel面向用户来说是无锁的,但其内部实现是依靠锁来完成的。
  再来看下从channel中接收数据:
  在编译阶段,v := <-c 被转换成了对函数runtime.chanrecv1的调用:
  对照汇编进一步确认:
  上图是receive函数的汇编逻辑,当该函数被调用时,ax寄存器里的值是receive函数的参数c,即channel变量。
  上图选中行,是v := <-c的汇编代码,它先将0x10(SP)开始的8字节内存清零,然后再将该内存的地址赋值给bx,最后调用runtime.chanrecv1。
  由此我们可以推测,runtime.chanrecv1函数应该有两个参数,一个是channel变量,另一个是内存地址,用于存放要接收到的数据。
  看下runtime.chanrecv1:
  参数类型与个数和我们推测的一样,它又调用了chanrecv:
  chanrecv是从channel中接收数据的主体逻辑,其大致步骤请参考上图中的注释,同时也可以结合上文提到的,hchan结构体中各字段的意义,来理解这段代码。
  和chansend类似,chanrecv的主体逻辑也是在加锁下完成的。
  以上就是channel的创建,发送数据,接受数据等主要操作的实现,了解这些实现,就算是对channel有一个比较好的理解了。
  但除此之外,channel还有一些细节知识,需要我们注意。
  1. 向nil channel发送数据会永久阻塞
  上图示例中是在向nil channel发送数据,但似乎没成功,并不像之前说的,向nil channel发送数据会永久阻塞。
  其实,这个错误是go内部检查死锁的机制,它并不是由向nil channel发送数据引起的。
  比如,下面的写法也会报这个错:
  上图示例中创建了一个unbuffered channel,然后向其发送数据,也报错了,因为这种写法会自己阻塞自己。
  那如何不报这个错,然后可以看到,向nil channel发送数据会永久阻塞呢?
  看下面这个例子:
  再开一个goroutine就好了,在这个示例中,c <- 8 会一直阻塞,没有返回。
  向nil channel发送数据会永久阻塞,对应的底层实现为:
  2. 向closed channel发送数据会发生run-time panic
  对应的底层实现为:
  3. 从nil channel中接收数据会永久阻塞
  对应的底层实现为:
  4. 从closed channel中接收数据,会返回channel元素类型的zero value
  对应的底层实现为:
  从上图中还可以得出一个结论,就是即使channel被关闭了,如果channel buffer中有数据,还是会正常返回数据。
  5. 从channel中接收数据可以有两个返回值,第二个返回值可近似表示channel是否已关闭
  该示例main函数的汇编代码:
  由上面的选中行可知,编译器将v, ok := <-c转成了对runtime.chanrecv2函数的调用:
  chanrecv2除了将从channel中接收的数据,拷贝到elem指针指向的内存外,还返回了一个received布尔值。
  当channel被关闭后,且其buffer中没有数据,再从channel中接收数据,chanrecv2返回的received值就为false,表示channel已经被关闭了。
  6. 对channel的len和cap操作是无锁的
  其main函数的汇编为:
  上图中第13行汇编 MOVQ 0(AX), CX 表示的是示例中的len(c) 操作,第21行汇编 MOVQ 0x8(CX), CX 表示的是示例中的 cap(c) 操作。
  由上图可见,对channel的len和cap操作,在汇编层面都是一条mov指令,并不像之前的,比如对channel的接收操作,是转换成对runtime.chanrecv1函数的调用,且在该函数中,有加锁解锁操作。
  综上可知,对channel的len和cap操作是无锁的。
  那为什么这两条mov指令,就可以获得channel的len和cap值呢?
  首先看上图汇编,13行中的ax和21行中的cx,存放的都是新建channel结构体的地址。
  那这两条mov指令的意思是:
  MOVQ 0(AX), CX -> 将channel结构体偏移量为0位置上的8字节放入cx中
  MOVQ 0x8(CX), CX -> 将channel结构体偏移量为8位置上的8字节放入cx中
  再看下channel结构体的定义:
  该结构体偏移量为0位置上的8字节,就是qcount,即当前channel buffer中已经缓冲的元素个数,也就是len(c)。
  偏移量为8位置上的8字节,就是dataqsiz,即当前channel buffer中最大能缓冲的元素个数,也就是cap(c)。
  7. close一个receive-only channel会编译时报错
  对应的编译器实现:
  8. close一个nil channel或closed channel会发生runtime panic
  对应的底层实现为:
  9. channel的for range形式
  for range形式其实是语法糖,在编译阶段,其会被转换成类似上图注释那样的for循环。
  对应的编译器转换代码为:
  我们也可以根据汇编,看for range转化后的样子:
  由上图可见,在汇编层面,其一直在调用runtime.chanrecv2函数,这个函数上面我们也提到过,就是对应于v, ok := <-c操作。
  channel的select形式在go内的实现比较复杂,我们来分步讲下。
  10. 空select语句永久阻塞
  对应的底层实现为:
  上图中的block对应于runtime.block函数:
  即永久阻塞。
  11. 只有一个case的情况,会直接转换成对channel的操作,等价于没有select部分:
  通过上图的汇编可见,示例中操作1和2是等价的。
  编译器中对一个case的转换代码为:
  12. 有两个case,且其中一个是default,会转换成对channel的非阻塞操作,如果没成功,则会执行default语句:
  上图中的runtime.selectnbsend就是c <- 1的非阻塞版,其源码为:
  selectnbsend函数内会调用chansend,该函数正是我们之前说的,向channel发送数据的函数,其中false参数表示该发送是非阻塞的。
  上图中的selectnbrecv也是非阻塞的从channel中接收数据,对应于select只有两个case,其中一个case是v <- c,另外一个是default的情况。
  编译器对该类情况的转换代码为:
  13. 其他情况就是select的通用形式了,编译器会把select语句转换成对runtime.selectgo函数的调用:
  对应的汇编代码:
  对应的编译器转换代码:
  selectgo函数会在各个已经就绪的channel里,随机选择一个执行,因为逻辑非常多,这里就展示下该函数的代码位置,有兴趣的可以自己看下:
  有关selectgo是随机选择就绪channel的,我们可以写个测试验证下:
  看到没,两个值基本相同。
  好了,以上就是各种channel操作对应的底层实现,希望通过此篇文章,能让大家对golang中的channel有更好的了解。

为什么头胎不要轻易打掉?跟迷信无关,很多女性后悔明白得太晚周末,带着两个孩子去看望大姨,和大姨聊天时,大姨看着孩子忽然有感而发如果佳宁不打掉孩子,估计现在跟沐沐差不多大了吧!佳宁是大姨的小女儿,也是我表妹。刚结婚那会儿,因为没玩够,怀上孩全面二胎后,第一批生二胎的人,后悔了吗文银花表姐的女儿7岁了,在女儿1岁多时就被婆家和娘家催生二胎,但表姐都以女儿还小,大大再说为由拒绝了。但最近她对要二胎更纠结了,她说眼看我也快到高龄产妇的边缘了,我也不知道我是真的拥有恋爱脑的周迅算是渣女吗?父母的婚姻,孩子总会继承点东西文银花周迅官宣离婚,让大家并不意外,这个为爱情而生的女人,如果一直相守一个人到终老才稀奇呢!一部大明宫词让观众认识了这位眼睛里闪着灵气的小太平公主一部龙门飞甲让人们记住了那个女扮男衡水中学的学霸的言论遭热议,孩子,你奋斗的样子很可爱文银花高考前夕,衡水中学高三学霸张锡峰在某演讲中,因一句我就是一只来自乡下的土猪,也要立志去拱大城市里的白菜,在网上迅速激起了千层浪。有人讽刺说这是典型的凤凰男,吃相真难看好臭的三祖国70华诞这盛世如你所愿今天,9月30日,是第六个烈士纪念日,是每个中国人都应该铭记的日子。有这样一群人因为坚信我们信仰的主义,乃是宇宙真理。为了免除下一代的苦难他们甘愿牺牲自己的一切!如今,新时代的中国来了解一下内蒙古的丰镇月饼中秋始于唐初,兴于宋朝,明代正式成为一大重要传统节日。宋时皇贵爱吃一种宫饼,民间称为月团,中秋食月饼见于武林旧事。明以后关于中秋赏月祭月的记载便多了起来,宛署杂记有记中秋时节百姓制黄河突然出现白花花的一片死猪,来历不明3月21日,黄河大堤内出现大量不明来源死猪引发关注。当晚,内蒙古达拉特旗政府就此事做出回应已成立应急处理领导小组,全旗沿黄河各苏木镇正对类似情况进行全面排查。事件起于3月16日,记家长会本想出头,不料却出丑,孩子都感觉分外脸红对于很多父母来说,到孩子的学校开家长会算是一件比较隆重的事情。因为要和老师以及其他的同班家长见面,自己的表现关系到孩子的面子问题,表现好了能给孩子长脸,而自己要是太寒酸的话,就会给进产房后这些囧事,产后和老公绝口不提,自己回想都感觉尴尬对于孕妇而言,进入产房那一刻是自己期待已久的同时,毕竟出了产房就能够成功晋级为宝妈了。不过,分娩的体验却是不经历的人无法感同身受的,产妇们不仅要承受着难以想象的身体之痛,同时还要忍第一次胎动原来是这种感觉,很多妈妈都把它当成肠胃蠕动了自从怀孕,肚里的小生命就成了孕妈们最大的期待和挂念,孕妈随时关注着肚子里的动态,也总期待什么时候能感受到胎动的奇妙。每次肚皮的一点动态都会以为是胎动,让孕妈特别兴奋和幸福。其实有时不上班,没事做,感觉被社会抛弃了(健康生活低卡饮食26)每天都自己给自己做饭吃,时间一长也觉得很难,不知道吃什么。今天早上去市场,看着各式各样的菜和肉类,就是不知道该买哪个。路过每个菜摊,摊主都会问同样的一句话吃啥呦?我答不知道,还没想
把握双减限制游戏契机,构建孩子多元化生命价值科学育儿底层思维陶勇2021年8月30日,国家新闻出版署下发关于进一步严格管理切实防止未成年人沉迷网络游戏的通知,要求所有网络游戏企业仅可在周五周六周日和法定节假日每日20时至21今年买房太难了9个城市出台了限价令!这个城市的下跌了近40近日,张家口市住房和城乡建设局张家口市财政局等四部门联合发布关于进一步加快完善房地产长效机制的通知。新取得预售许可证的项目不得以低于记录价格85的价格出售的内容之一被外界称为限价令十三香翻车?一些iPhone13照片有马赛克图案苹果的iPhone13系列于9月24日正式上市。目前,许多用户已经收到并开始使用iPhone13系列手机。今天,一些用户报告说他们的苹果iPhone13出现了问题。拍照时,马赛克会一九七七年纽约大停电引发了九个月后的婴儿潮?最近,一篇关于1977年纽约停电和9个月后婴儿潮的文章截图再次在中国社交网络上传播。纽约大停电确实引起了相当大的轰动,美国主流媒体报道了大停电九个月后的所谓婴儿潮。但人口统计数据似鸿星尔克悄悄地向山西捐赠物资。网友你怕我们太热情了吗?近日发生的特大洪灾,山西省11个市76个县(市区)175多万人受灾,12万多人紧急搬迁,284。96万亩农作物受灾。山西省财政厅会同多个部门下发了5000万元省级洪灾救灾基金,全力芯片行业领头羊宣布利好消息,出货量突破3亿,打破了外国的垄断此前关于苹果手机的新闻发现,在拆卸后,其核心部件中没有一个来自我们,这引起了激烈的讨论。除芯片外,日本美国和韩国还提供存储设备屏幕和摄像头,这在一定程度上暴露了我们在这些核心技术领房地产市场的金九银十,央行给购房者吃定心丸9月29日,央行与中国银行业和保险监督管理委员会联合举办了房地产金融研讨会。研讨会强调,金融机构要按照法制化市场化的原则,与有关部门和地方政府合作,共同维护房地产市场的稳定健康发展提高生育率是一场持久战最近,各地纷纷出台鼓励生育的配套政策,表明提高生育率已成为共识,激励政策可能会继续演变。然而,从全球案例来看,要在一夜之间提高生育率是不可能的,我们应该准备打持久战。北京市朝阳区近中国石油大学(华东地区)和东营道别,正式注册地点变更为青岛中国石油大学(华东地区)是国家211工程首批首批重点大学之一,并于2017进入国家双一流大学行列。最近,中国石油大学(华东地区)迎来了一个里程碑,它的注册地点正式调整到青岛。学校在用个棉条就那么可怕?没文化更可怕吧最近在网上看到个帖子,一个女生因为月经期间用了卫生棉条被男友甩了。究其原因,竟是男生得知女生用塞入体内的方式解决月经,以此断定她不洁身自好,不是个好女孩。我妈说,用棉条的女孩没几个书店运营新模式文化赛道新蓝海项目研发始于2019年,商标获准于2020年,2021年金秋时节,吉纽书殿全国开设加盟合作。吉纽书殿项目响应全民阅读和双减政策,创造书店运营新模式,站在时代风口,享受政策红利,集图