很多朋友对于回收类网站源码分享和回收网址不太懂,今天就由小编来为大家分享,希望可以帮助到大家,下面一起来看看吧!
概述
channel是golang最重要的一个结构,是区别于其他高级语言的最重要的特色之一,也是goroutine通信是必须要具备的要素之一。很多人用过它,但是很少有人彻底理解过它,甚至c<-x,<-c这样的语法可能都记不清晰,怎么办?本文教你从源码编译器的角度全方位的剖析channel的用法。
channel是什么
本质上就实现角度来讲,golang的channel就是一个环形队列(ringbuffer)的实现。我们称chan为管理结构,channel里面可以放任何类型的对象,我们称之为元素。
我们从channel的使用姿势入手,讲解最详细的channel使用方法。
channel数据结构
channel使用
阻塞式channel:
vara=make(chanint)
非阻塞channel:
vara=make(chanint,10)
阻塞和非阻塞关键就在是否有capacity。没有capacity的话,channel也就只是个同步通信工具。
向channel中发送内容:
ch:=make(chanint,100)\nch<-1
从channel中接收内容:
vari=<-ch
关闭channel:
close(ch)
注意,已关闭的channel,再次关闭会panic
close(ch)\nclose(ch)//panic:closeofclosedchannel
在channel关闭时自动退出循环
funcmain(){\nch:=make(chanint,100)\nforelem:=rangech{//主要就是这里的forrange…\nfmt.Println(i)\n}\n}
获取channel中元素数量、buffer容量:
funcmain(){\nch:=make(chanint,100)\nch<-1\nfmt.Println(len(ch))//1\nfmt.Println(cap(ch))//100\n}
注意,len和cap并不是函数调用。编译后是直接去取hchan的field了。
closedchannel
被关闭的channel不能再向其中发送内容,否则会panic
ch:=make(chanint)\nclose(ch)\nch<-1//panic:sendonclosedchannel
注意,如果closechannel时,有sendergoroutine挂在channel的阻塞发送队列中,会导致panic:
funcmain(){\nch:=make(chanint)\ngofunc(){ch<-1}()//panic:sendonclosedchannel\ntime.Sleep(time.Second)\ngofunc(){close(ch)}()\ntime.Sleep(time.Second)\nx,ok:=<-ch\nfmt.Println(x,ok)\n}
close一个channel会唤醒所有等待在该channel上的g,并使其进入Grunnable状态,这时这些writergoroutine会发现该channel已经是closed状态,就panic了。
在不确定是否还有goroutine需要向channel发送数据时,请勿贸然关闭channel。
可以从已经closed的channel中接收值:
ch:=make(chanint)\nclose(ch)\nx:=<-ch
如果channel中有值,这里特指带buffer的channel,那么就从channel中取,如果没有值,那么会返回channel元素的0值。
区分是返回的零值还是buffer中的值可使用comma,ok语法:
x,ok:=<-ch
若ok为false,表明channel已被关闭,所得的是无效的值。
nilchannel
不进行初始化,即不调用make来赋值的channel称为nilchannel:
varachanint
关闭一个nilchannel会直接panic
varachanint\nclose(a)//panic:closeofnilchannel
closeprinciple
一个sender,多个receiver,由sender来关闭channel,通知数据已发送完毕。
一旦sender有多个,可能就无法判断数据是否完毕了。这时候可以借助外部额外channel来做信号广播。这种做法类似于donechannel,或者stopchannel。
如果确定不会有goroutine在通信过程中被阻塞,也可以不关闭channel,等待GC对其进行回收。
源码分析
hchan
hchan是channel在runtime中的数据结构
//channel在runtime中的结构体\ntypehchanstruct{\n//队列中目前的元素计数\nqcountuint//totaldatainthequeue\n//环形队列的总大小,ch:=make(chanint,10)=>就是这里这个10\ndataqsizuint//sizeofthecircularqueue\n//void*的内存buffer区域\nbufunsafe.Pointer//pointstoanarrayofdataqsizelements\n//sizeofchan中的数据\nelemsizeuint16\n//是否已被关闭\ncloseduint32\n//runtime._type,代表channel中的元素类型的runtime结构体\nelemtype*_type//elementtype\n//发送索引\nsendxuint//sendindex\n//接收索引\nrecvxuint//receiveindex\n//接收goroutine对应的sudog队列\nrecvqwaitq//listofrecvwaiters\n//发送goroutine对应的sudog队列\nsendqwaitq//listofsendwaiters\n//lockprotectsallfieldsinhchan,aswellasseveral\n//fieldsinsudogsblockedonthischannel.\n//\n//DonotchangeanotherG&34;makechan:invalidchannelelementtype&34;makechan:badalignment&34;makechan:sizeoutofrange&39;sarereferencedfromtheirowningthreadsotheycan&34;chansend(nilchan)&34;unreachable&39;readyforsending&39;notreadyforsending&39;tclosedduringthefirstobservation.\nif!block&&c.closed==0&&((c.dataqsiz==0&&c.recvq.first==nil)||\n(c.dataqsiz>0&&c.qcount==c.dataqsiz)){\nreturnfalse\n}\nvart0int64\nifblockprofilerate>0{\nt0=cputicks()\n}\nlock(&c.lock)\n//channel已被关闭,panic\nifc.closed!=0{\nunlock(&c.lock)\npanic(plainError(&34;))\n}\nifsg:=c.recvq.dequeue();sg!=nil{\n//Foundawaitingreceiver.Wepassthevaluewewanttosend\n//directlytothereceiver,bypassingthechannelbuffer(ifany).\n//寻找一个等待中的receiver\n//越过channel的buffer\n//直接把要发的数据拷贝给这个receiver\n//然后就返\nsend(c,sg,ep,func(){unlock(&c.lock)},3)\nreturntrue\n}\n//qcount是buffer中已塞进的元素数量\n//dataqsize是buffer的总大小\n//说明还有余量\nifc.qcount<c.dataqsiz{\n//Spaceisavailableinthechannelbuffer.Enqueuetheelementtosend.\nqp:=chanbuf(c,c.sendx)\n//将goroutine的数据拷贝到buffer中\ntypedmemmove(c.elemtype,qp,ep)\n//将发送index加一\nc.sendx++\n//环形队列,所以如果已经加到最大了,就会0\nifc.sendx==c.dataqsiz{\nc.sendx=0\n}\n//将buffer的元素计数+1\nc.qcount++\nunlock(&c.lock)\nreturntrue\n}\nif!block{\nunlock(&c.lock)\nreturnfalse\n}\n//Blockonthechannel.Somereceiverwillcompleteouroperationforus.\n//在channel上阻塞,receiver会帮我们完成后续的工作\ngp:=getg()\nmysg:=acquireSudog()\nmysg.releasetime=0\nift0!=0{\nmysg.releasetime=-1\n}\n//Nostacksplitsbetweenassigningelemandenqueuingmysg\n//ongp.waitingwherecopystackcanfindit.\n//打包sudog\nmysg.elem=ep\nmysg.waitlink=nil\nmysg.g=gp\nmysg.isSelect=false\nmysg.c=c\ngp.waiting=mysg\ngp.param=nil\n//将当前这个发送goroutine打包后的sudog入队到channel的sendq队列中\nc.sendq.enqueue(mysg)\n//将这个发送g从Grunning->Gwaiting\n//进入休眠\ngoparkunlock(&c.lock,&34;,traceEvGoBlockSend,3)\n//someonewokeusup.\n//这里是被唤醒后要执行的代码\nifmysg!=gp.waiting{\n//先判断当前是不是合法的休眠中\nthrow(&34;)\n}\ngp.waiting=nil\nifgp.param==nil{\nifc.closed==0{\nthrow(&34;)\n}\n//唤醒后发现channel被人关了,气啊\npanic(plainError(&34;))\n}\ngp.param=nil\nifmysg.releasetime>0{\nblockevent(mysg.releasetime-t0,2)\n}\nmysg.c=nil\nreleaseSudog(mysg)\nreturntrue\n}\n//sendprocessesasendoperationonanemptychannelc.\n//Thevalueepsentbythesenderiscopiedtothereceiversg.\n//Thereceiveristhenwokenuptogoonitsmerryway.\n//Channelcmustbeemptyandlocked.sendunlockscwithunlockf.\n//sgmustalreadybedequeuedfromc.\n//epmustbenon-nilandpointtotheheaporthecaller&39;tneedtocheckep,asitisalwaysonthestack\n//orisnewmemoryallocatedbyreflect.\n//如果在nilchannel上进行recv操作,那么会永远阻塞\nifc==nil{\nif!block{\n//非阻塞的情况下\n//要直接返回\n//非阻塞出现在一些select的场景中\n//参见selectnbrecv/selectnbrecv2\nreturn\n}\n//当前goroutine:Grunning->Gwaiting\n//其实就是该goroutine直接泄露leak了\ngopark(nil,nil,&34;,traceEvGoStop,2)\n//放个throw有点莫名其妙\n//不过这段代码确实永远达到不了\nthrow(&34;)\n}\n//Fastpath:checkforfailednon-blockingoperationwithoutacquiringthelock.\n//\n//Afterobservingthatthechannelisnotreadyforreceiving,weobservethatthe\n//channelisnotclosed.Eachoftheseobservationsisasingleword-sizedread\n//(firstc.sendq.firstorc.qcount,andsecondc.closed).\n//Becauseachannelcannotbereopened,thelaterobservationofthechannel\n//beingnotclosedimpliesthatitwasalsonotclosedatthemomentofthe\n//firstobservation.Webehaveasifweobservedthechannelatthatmoment\n//andreportthatthereceivecannotproceed.\n//\n//Theorderofoperationsisimportanthere:reversingtheoperationscanleadto\n//incorrectbehaviorwhenracingwithaclose.\nif!block&&(c.dataqsiz==0&&c.sendq.first==nil||\nc.dataqsiz>0&&atomic.Loaduint(&c.qcount)==0)&&\natomic.Load(&c.closed)==0{\n//非阻塞且没内容可收的情况下要直接返回\n//两个bool的零值就是false,false\nreturn\n}\nvart0int64\nifblockprofilerate>0{\nt0=cputicks()\n}\nlock(&c.lock)\n//当前channel中没有数据可读\n//直接返回notselected\nifc.closed!=0&&c.qcount==0{\nunlock(&c.lock)\nifep!=nil{\ntypedmemclr(c.elemtype,ep)\n}\nreturntrue,false\n}\n//sender队列中有sudog在等待\n//直接从该sudog中获取数据拷贝到当前g即可\nifsg:=c.sendq.dequeue();sg!=nil{\n//Foundawaitingsender.Ifbufferissize0,receivevalue\n//directlyfromsender.Otherwise,receivefromheadofqueue\n//andaddsender&34;chanreceive&34;Gwaitinglistiscorrupted&39;sdataisputinthe\n//channelbuffer.\n//Channelcmustbefullandlocked.recvunlockscwithunlockf.\n//sgmustalreadybedequeuedfromc.\n//Anon-nilepmustpointtotheheaporthecaller&34;closeofnilchannel&34;closeofclosedchannel&39;vedroppedthechannellock.\nforglist!=nil{\ngp:=glist\nglist=glist.schedlink.ptr()\ngp.schedlink=0\n//诗g的状态切换到Grunnable\ngoready(gp,3)\n}\n}
回收类网站源码分享的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于回收网址、回收类网站源码分享的信息别忘了在本站进行查找哦。
