生活工程体验信仰哲学精神
投稿投诉
精神世界
探索历史
哲学文学
艺术价值
信仰创造
境界审美
体验技术
技能工具
工程信息
医学生产
生活运用
操作能力

golang各种channel操作的底层实现

9月5日 终离去投稿
  首先,未初始化的channel变量值为nil:
  channel底层其实就是个指针,这个下面会讲,所以其nil值,在底层就是用0表示的,如上面的输出。
  上图是main函数的汇编,其中选中的两行,就是调用p函数的逻辑。
  p函数的参数是通过ax寄存器传递的,由上图可见,在调用p函数之前,ax寄存器的值,通过xorl指令进行了清零,这也是channel变量的nil值,在底层是用0表示的另一个佐证。
  channel是通过make函数创建的,make可以创建unbufferedchannel,也可以创建bufferedchannel:
  make其实并不是一个真正的函数,它会在编译阶段,被go编译器替换为对runtime。makechan函数的调用:
  该替换信息也可以通过汇编查看:
  上图中选中行为make函数的汇编逻辑,其中hello。go:8的第四行,和hello。go:11的第三行,都是在调用runtime。makechan函数。
  在看runtime。makechan函数之前,我们先通过上图的汇编,看下make创建unbufferedchannel和bufferedchannel的方式有什么不同。
  源码中第8行是创建unbufferedchannel,对应到汇编里,就是上图中hello。go:8的那4行。
  源码中第11行是创建bufferedchannel,对应到汇编里,就是上图中hello。go:11的那4行
  由上图可见,hello。go:8和hello。go:11的逻辑基本相同,都是先将要创建channel的类型,放到ax寄存器里,然后将要创建channel的buffersize,放到bx寄存器里,最后调用runtime。makechan函数,转入真正的创建channel逻辑。
  这里需要说明一下,go函数之间的调用,其参数和结果的传递,主要是通过寄存器来完成的(go1。17之后,之前是通过栈),对于上面的makechan函数来说,第一个参数是通过ax寄存器传递的,第二个参数是通过bx寄存器传递的。
  有关go函数之间调用,参数和结果传递方式的具体规则,请参考以下文档:
  https:github。comgolanggoblobmastersrccmdcompileabiinternal。md
  再回到上面的问题,由上图汇编可见,创建unbufferedchannel和bufferedchannel的流程是一样的,唯一区别就是bx寄存器的值不同,即指定的buffersize不一样。
  hello。go:8是创建unbufferedchannel,bx的值是0,hello。go:11是创建bufferedchannel,bx的值是8,即我们在源码中指定的值。
  所以,make(chanint)其实等价于make(chanint,0),即buffersize等于0。
  接下来我们看一下runtime。makechan函数的实现:
  连接上文,参数t,即channel的类型,是通过ax寄存器传递的,参数size,即channel的buffersize,是通过bx寄存器传递的。
  上面我们也提到,channel变量底层其实就是个指针,该指针的类型,就是上图中makechan函数的返回类型hchan:
  hchan结构体各字段的用途是:
  qcount表示的是当前channel的buffer里已经缓冲了几个元素。如果是unbufferedchannel,该字段一直为0。
  dataqsiz表示的是当前channel的buffer里最多可缓冲几个元素,即上文我们提到的buffersize。如果是unbufferedchannel,该字段一直为0。
  buf指向的是用于接受缓冲元素的总内存,我们可以把它理解成一个数组,数组的元素类型就是channel的元素类型,数组的最大容量,就是上面dataqsiz的值。如果是unbufferedchannel,该buf不用分配。
  elemsize表示的是channel元素类型的大小。
  closed表示的是当前channel是否已关闭,即调用过close(c)方法。
  elemtype表示的是channel的元素类型。
  sendx表示的是下一次向channel中发送数据,该数据会被拷贝到buf字段表示的元素数组的位置,当该位置超过数组最大值以后,会从0重新开始。如果是unbufferedchannel,该字段一直为0。
  recvx表示的是下一次从channel中接收数据,该接收会从buf字段表示数组的recvx位置拷贝数据到目标内存。如果是unbufferedchannel,该字段一直为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的值,然后写个例子看下其输出。
  示例代码:
  gobuild构建程序并执行:
  上图选中行中,第一行是make(chanS)的输出,该make创建的channel,元素大小为80字节,可缓冲元素个数为0。
  第二行是make(chanS,8)的输出,它创建的channel,元素大小也为80字节,可缓冲元素个数为8。
  因为channel元素传递是拷贝操作,所以对于这个示例来说,每次send或receive时,都要拷贝80字节,此种情况下,就应该考虑将该channel改为传递S的指针,而不是S本身。
  接下来看下如何向channel中发送数据:
  也是在编译阶段,cv被转成了对runtime。chansend1函数的调用:
  同样,我们也可以根据汇编代码得出该信息:
  上图是send函数的汇编逻辑,其中和发送相关的,是上图中的选中行,即hello。go:10的那4行。
  根据go的callingconvention可知,上面示例中的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。向nilchannel发送数据会永久阻塞
  上图示例中是在向nilchannel发送数据,但似乎没成功,并不像之前说的,向nilchannel发送数据会永久阻塞。
  其实,这个错误是go内部检查死锁的机制,它并不是由向nilchannel发送数据引起的。
  比如,下面的写法也会报这个错:
  上图示例中创建了一个unbufferedchannel,然后向其发送数据,也报错了,因为这种写法会自己阻塞自己。
  那如何不报这个错,然后可以看到,向nilchannel发送数据会永久阻塞呢?
  看下面这个例子:
  再开一个goroutine就好了,在这个示例中,c8会一直阻塞,没有返回。
  向nilchannel发送数据会永久阻塞,对应的底层实现为:
  2。向closedchannel发送数据会发生runtimepanic
  对应的底层实现为:
  3。从nilchannel中接收数据会永久阻塞
  对应的底层实现为:
  4。从closedchannel中接收数据,会返回channel元素类型的zerovalue
  对应的底层实现为:
  从上图中还可以得出一个结论,就是即使channel被关闭了,如果channelbuffer中有数据,还是会正常返回数据。
  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行汇编MOVQ0(AX),CX表示的是示例中的len(c)操作,第21行汇编MOVQ0x8(CX),CX表示的是示例中的cap(c)操作。
  由上图可见,对channel的len和cap操作,在汇编层面都是一条mov指令,并不像之前的,比如对channel的接收操作,是转换成对runtime。chanrecv1函数的调用,且在该函数中,有加锁解锁操作。
  综上可知,对channel的len和cap操作是无锁的。
  那为什么这两条mov指令,就可以获得channel的len和cap值呢?
  首先看上图汇编,13行中的ax和21行中的cx,存放的都是新建channel结构体的地址。
  那这两条mov指令的意思是:
  MOVQ0(AX),CX将channel结构体偏移量为0位置上的8字节放入cx中
  MOVQ0x8(CX),CX将channel结构体偏移量为8位置上的8字节放入cx中
  再看下channel结构体的定义:
  该结构体偏移量为0位置上的8字节,就是qcount,即当前channelbuffer中已经缓冲的元素个数,也就是len(c)。
  偏移量为8位置上的8字节,就是dataqsiz,即当前channelbuffer中最大能缓冲的元素个数,也就是cap(c)。
  7。close一个receiveonlychannel会编译时报错
  对应的编译器实现:
  8。close一个nilchannel或closedchannel会发生runtimepanic
  对应的底层实现为:
  9。channel的forrange形式
  forrange形式其实是语法糖,在编译阶段,其会被转换成类似上图注释那样的for循环。
  对应的编译器转换代码为:
  我们也可以根据汇编,看forrange转化后的样子:
  由上图可见,在汇编层面,其一直在调用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就是c1的非阻塞版,其源码为:
  selectnbsend函数内会调用chansend,该函数正是我们之前说的,向channel发送数据的函数,其中false参数表示该发送是非阻塞的。
  上图中的selectnbrecv也是非阻塞的从channel中接收数据,对应于select只有两个case,其中一个case是vc,另外一个是default的情况。
  编译器对该类情况的转换代码为:
  13。其他情况就是select的通用形式了,编译器会把select语句转换成对runtime。selectgo函数的调用:
  对应的汇编代码:
  对应的编译器转换代码:
  selectgo函数会在各个已经就绪的channel里,随机选择一个执行,因为逻辑非常多,这里就展示下该函数的代码位置,有兴趣的可以自己看下:
  有关selectgo是随机选择就绪channel的,我们可以写个测试验证下:
  看到没,两个值基本相同。
  好了,以上就是各种channel操作对应的底层实现,希望通过此篇文章,能让大家对golang中的channel有更好的了解。
投诉 评论 转载

精神病失眠与常人失眠有什么区别?影响失眠的原因是什么呢?失眠主要表现为入睡困难、早醒等,按照时间来分可以分为短暂性失眠、短期性失眠,以及长期性失眠,通常短暂性失眠和饮食、环境、运动相关的诱因有关,多数不需要治疗,12天就可以明显的好……好好读书哦最近读了本知识管理类的书,《好好学习》给我留下的深刻的印象。说来惭愧,我平时很少读书,长这么大读过的书不超过100本,其中一少半还是YY小说!然而作者却能在高负荷创业过程中一年……2016年3月1日我国首部反家暴法今日起施行我国首部反家暴法今日起施行全国人大常委会12月27日表决通过了《中华人民共和国反家庭暴力法》,作为我国首部反家暴法,该法共六章38条,将于2016年3月1日起施行。……没出息的孩子是来报恩的?不绝对,孩子是否报恩得看这几方面文乔治麻麻经常有人说有出息的孩子,父母老了之后指望不上,因为孩子读书好了,有出息了,将来离开父母出去工作的几率会比较大,就不能常常陪伴父母了。这个理论有些道理,确实……golang各种channel操作的底层实现首先,未初始化的channel变量值为nil:channel底层其实就是个指针,这个下面会讲,所以其nil值,在底层就是用0表示的,如上面的输出。上图是main函数……7岁男童,从长期积食到脾胃正常,医生坚持3个习惯,有助消积案例分析:6岁的洋洋突然高烧不退,可吓坏了父母,父母连夜把他送到医院诊治,医生经过检查后才发现,原来,杨洋竟然是一种慢性病情影响所致,这种病症就是积食!积食是指饮食……家长一定要重视的儿童十大心理问题一、孩子很容易生气,是因为你对他肯定太少,他心里的不满太多二、孩子总爱打扰你,因为你给他的关注不够三、孩子不顾别人感受,因为你总是命令他,从来不顾她的感受四、……手头有60万,今明两年应该买房还是存钱?银行行长给出良心建议理财,一直都是人们茶余饭后喜爱讨论的话题。在互联网时代的大背景下,理财的方式多种多样,基金、股票、存款、债券等都是理财的方式。但理财这个词并不是近代才开始流行的,早在古代就有相……过年蒸馍别忘蒸年糕,双色粘米1把豆简单蒸一蒸,层层甜糯寓意好过年蒸馍别忘蒸粘糕,双色粘米1把红豆,简单蒸一蒸,层层甜糯,寓意好。百节年为首,春节是中华民族最隆重的传统节日,也是个欢乐祥和、合家团圆之日。过年不仅是传统文化的体现与沿……明明有对象却不能有X生活,这个病让很多人苦不堪言医学界妇产科频道以下文章来源于医女正传,作者飒姐666666很多人都不知道,自己有盆腔炎要说让人没有X生活的原因,那肯定是有很多,比如活了几十年还是个单身,就……孩子做家务劳动,父母到底要不要给经济奖励呢?发生在浙江的,一个17岁男孩答应爸爸在暑假的时候,到店里帮忙,并且约定帮忙一天,爸爸给孩子100元作为零花钱。就在父亲2天给了他200元钱以后,孩子竟然心生不满,要求把自己的爸……消防服穿戴步骤消防服种类有哪些消防服穿戴方法消防服是保护活跃在消防第一线的消防队员人身安全的重要装备品之一,它不仅是火灾救助现场不可或缺的必备品,也是保护消防队员身体免受伤害的防火用具。下面就让小编为您介绍消防服种类和消……
未来怎样买房才能避免踩坑第十一届临沂市花沂州海棠文化旅游节摄影大赛启动领克09全新的豪华智能旗舰suv究竟有什么魅力故事思维就是这么特别好好学习拆书分享花2880元在深夜直播间沉迷拆卡,男人的快乐究竟有多简单?租写字楼需要哪几步?这份新手攻略请收好罗瑞卿果断下令立即抓捕,他是潜伏到周总理身边的日本特务杭州增设午睡课,6所中小学试睡后,家长建议全国推行那个有问题的孩子,真的很爱你陈婷屡遭抛弃如果不做错3件事,一切还能补救吗?24女子生二胎突发血癌,丈夫问你死后我什么时间可以再娶订立自书遗嘱的条件是怎么要求的世界上最大的水族馆,乔治亚水族馆(全球最大水族馆排名)皇帝都羡慕的男人,娶130位老婆,210个儿女遍布全球各地(卡塔尔世界杯)前瞻亚洲球队能走多远?人生成败,不取决于你是否努力和聪明,关键在于你做选择的能力女人铸造靓丽容颜如何抓住“食”机prohands吉他指力器怎么用?吉他指力器的正确使用方法慢性气管炎的食疗方法是什么?中班科学教案有其父必有其子?郭麒麟与宋轶拍吻戏太搞笑,看郭德纲吻戏能笑瘫闷闷不乐什么意思?闷闷不乐典故介绍孩子独立自主性不够怎么办孩子独立自主性不够如何解决

友情链接:中准网聚热点快百科快传网快生活快软网快好知文好找