This is a creation in Article, where the information may have evolved or changed.
Go Channel implementation
After years of development, the go language has recently launched its first stable version. Go has a lot of unique features relative to C/C + +, such as providing fairly abstract tools such as channel and Goroutine. This paper mainly introduces the way of channel implementation.
Brief introduction
The channel has four operations:
- Create:
c = make(chan int)
- Send:
c <- 1
- Extraction:
i <- c
- Shut down:
close(c)
Depending on how it is created, the channel can also be divided into buffer channel and channel without buffer. The size of the buffer is specified by the second parameter of make, which defaults to 0, which means that there is no buffer. The way to create a channel with buffer is:c = make(chan int, 10)
The channel implementation is mainly in the file src/pkg/runtime/chan.c . Its data structure is as follows:
structhchan{uint32qcount;//total data in the quint32dataqsiz;//size of the circular quint16elemsize;boolclosed; Uint8elemalign; alg*elemalg;//interface for element typeuint32sendx;//send indexuint32recvx;//receive indexwaitqrecvq;//list of recv W aiterswaitqsendq;//list of send waiterslock;};
Send process
HchanThe two WaitQ( recvq and sendq) two queues in the Goroutine are saved, respectively, that are waiting to be extracted and sent from the channel. To send the channel without buffer as an example,
- If the Goroutine discovery that sends the data to the channel is
recvq not empty, a goroutine is taken from the, then the data is passed to it, the sending is recvq complete, and the sender Goroutine can continue execution. The extraction side goroutine The block state and can be scheduled for execution.
- Otherwise, the sender Goroutine is credited
sendq to the queue and the sender Goroutine into the block state, and the scheduling algorithm chooses other goroutine execution.
If the channel has buffer,
- If there is space in buffer, the data is stored in buffer and sent, and if
recvq there are waiting goroutine in the queue, one is taken out and awakened, waiting for the dispatch to execute. Sender Goroutine continues execution.
- If buffer is full, the sender Goroutine is
sendq queued, the sender goroutine into the block state, and the scheduling algorithm chooses other goroutine execution.
If you send data to a channel that has already been closed, the program will error and exit abnormally. As in the following program:
Package MainFunc Main () {c: = do (chan int) d: = make (chan int) go func () {<-dclose (c)} () d <-4c <-3}
Collecting data from the closed channel does not give an error and does not exit abnormally, but I am not sure what value to get. In addition, the implementation of the extraction and delivery is basically relative, it is no longer introduced.
Buffer space
The buffer space is next to the channel, which is allocated when the channel is created,
c = (hchan*) runtime Mal (n + hint*elem->size);
Which is the hint number of elements in buffer, will be saved in the other, together with the dataqsiz management buffer, qcount sendx and, respectively, the number of recvx elements in the buffer, the next send operation where the data is stored, and the next time the location of the data extracted. This buffer is a circular buffer.
Channel and select
Channel with SELECT statement, you can achieve the effect of multiplex, such as:
Select {Case <-c1:case <-C2:}
c1and c2 which channel has data to arrive first, which case executes first, no data, block live; there is data to randomly select a case execution in a fair way. The SELECT statement itself does not increase the channel operation, but its own implementation is also interesting:
- When select is blocked, the goroutine it resides on will be hung over multiple channel
sendq or recvq above. In the example above, the goroutine of the select will be hung on c1 the c2 and recvq , if there are another two goroutine at the same time to c1 c2 send the data separately, Then they will operate on the same goroutine (albeit a different channel), in which case they are either locked or atomically manipulated. This is why the dequeue reason to use runtime·cas , although the call dequeue was locked before, but that is to give sendq / recvq lock, not to goroutine locked.
- A SELECT statement inside a different goroutine may operate on the same set of channel, so there is a need to lock it. In the go implementation, each channel has its own lock, so select requires more than one lock, slightly careless, may lead to deadlock. The implementation of GO is to sort the channel's address (i.e.) with bubble sort
Hchan* , then lock it in turn.
- The last is how to achieve relative fairness.
Another argument for relative fairness is that the probability of each channel being selected is equal. The implementation is as follows:
for (i=0; i<sel->ncase; i++) Sel->pollorder[i] = I;for (i=1; i<sel->ncase; i++) {o = Sel->pollorder[i];j = Runtime Fastrand1 ()% (i+1); Sel->pollorder[i] = sel->pollorder[j];sel->pollorder[j] = o;}
What each iteration does is to i randomly select one of the previous elements and place it in the first i position. This algorithm is more difficult to understand than the programming pearls, because each element can be moved several times. We discuss in two ways that, for any position, the i elements that eventually fall into this position may come from i before (including i ) or i later.
If it is from the i previous (including i ), then it cannot be swapped out afterwards. So the probability that it stays in position i is (1/i) * i/(i+1) * (i+1)/(i+2) * ... * (n-1)/n = 1/n .
If it comes from i after (such as position k ), then i after the change, there can be no subsequent elements and i exchanges, so the probability is (1/k) * k/(k+1) * ... * (n-1)/n = 1/n .
From the above two cases, the probability that any element appears in i the position is 1/n .
Therefore, it pollorder is fair for each case to check whether the case can be executed in order.
Transferred from: http://alpha-blog.wanglianghome.org/2012/04/13/go-channel-implementation/