Photo taken on September 25, 2021 shows the Xixi Wetland in Hangzhou.

The last article mainly reflects some principles of channel indirectly through a practical example. The final article begins with some details, which will involve the source code.

Or from a simple code program to see.

We create an unbuffered channel and send data to it. Sending blocks because there is no read ready in the program. Let’s look at its underlying calls through assembly code.

From the figure we can see that the above send operation is actually called runtime.chansend1 while the program is running.

Chansend1 eventually calls Chansend, and the third argument to chansend, block, is a bool indicating whether the channel operation should block if it fails immediately.

What are the specific operations?

  • Sends data to an unbuffered channel and no receiver is currently ready.

  • Receive unbuffered channel data and no sender is currently ready.

  • Buffer channel is full. Send data to channel.

  • The buffer channel is empty and receives channel data

  • Send data to a nil channel. (Note that sending data to a nil channel does not cause panic.)

  • Receive data to a nil channel.

Encountering the above operation, without special treatment, would block our application until it was woken up.

Of course for to send | receive data channel to nil, subsequent never awakened the opportunity.

So if it’s a quick trial and error scenario, does it just change the block to false so it doesn’t block in the failed scenario?

Compile this code.

As you can see, this code compiled to call selectNbSend is still chansned, but the block passed in is false. So that if the operation fails, the program will not block.

Similarly we can derive the received call action.

So far we’ve seen,

  • Sends the data and finally calls runtime.chansend.

  • Receives data and finally calls runtime.chanrecv.

Let’s explain how these two functions operate underneath.

Again, we use an unbuffered channel and a buffered channel.

Let’s look at a simple program.

It’s worth noting that when you use Go func, you essentially call Runtime. newproc to create a G and then hand that G to the scheduler.

As for when G is scheduled to execute your code logic, it depends on the “mood” of the scheduler.

So the two G’s created above (let’s call them G1 and G2) can be regarded as we have submitted two g’s to the scheduler. We cannot guarantee which G will be executed by the scheduler, so we are not sure which of the two operations, send and receive, will be executed first.

Suppose g1 is first run by the scheduler and then executes the code ch<-struct{}{}.

If G2 is run by the scheduler first, then the code <-ch is executed.

Of course we can also translate the above code into a detailed flow chart of the unbuffered queue core.

There are three situations when a buffer channel sends. Think of the Courier delivery scenario in our previous article.

  • If the delivery cabinet is not full, put the delivery directly into the delivery cabinet. (If the buffer is not full, copy the sent data to the buffer.)

  • If the bin is full, the Courier will have to wait for it to be empty. (This corresponds to encapsulating the current G as sudog, then putting sudog into sendq, and finally suspending the current G)

  • If the customer is waiting at the time of delivery, just send the delivery directly to him (corresponding to if there is a waiting person when sending, directly copy the data to him).

Let’s create an example.

We create a channel with buffer 7. A buffer is used to store buffer elements. It is actually a circular array. Why is it circular? Because then you can reuse space.

There is no send or receive action, so qcount is 0, and both send (sendx) and receive (recvx) positions are 0.

Let’s look at the first case above. Buffer zone is not full,

This code is a little bit simpler. If the buffer is full, copy the current data to the send location of the buffer, sendx+1, qcount+1, and the whole process ends.

If the buffer is full, encapsulate the current G into sudog, put the sudog into the queue to wait for sending, and finally call gopark to suspend the current G, as mentioned above when there is no buffer.

Finally, if there is a waiting recipient at the time the message is sent, take the earliest waiting recipient from RECVQ and copy the sent data directly to him.

Send has two operations: Copy data —–> Wake up the waiting RECVQ.

What about the receive operation?

  • There’s my package in the delivery cabinet, so I’ll just take it. (There is data in the corresponding buffer, according to the location of read recvx data)

  • There’s no delivery for me yet, but the delivery guy called and said it was coming, so I’m going to look around downstairs. (The corresponding buffer has no data, encapsulate the current G into sudog, and then put it into recVQ waiting to receive messages).

  • I went to pick up one delivery, and one delivery guy dropped off my other because the delivery cabinet was full, and I was waiting. The buffer is full and there are waiting senders. At this time, I first go to the buffer to obtain the current recVX position data, and then I take out the earliest waiting sender from the waiting sender queue, copy the data he wants to send to the current position I read data (ensure the first-in, first-out order), and finally update the sending position and update position.

The first case is easy. The buffer is read directly from the current read position recvx, and the return value must be ignored to determine whether to copy the data to the current receive operation. Then move the recvx position, qcount–, and unlock it.

In the second case, the current G is encapsulated as sudog, the sudog is queued for receiving, and the current G is suspended by calling gopark. I drew this logic up here when there was no buffer.

The third case is a bit more complicated.

In this case, when retrieving a waiting sender, what happens to the receiver if we just take its sent data and return it? So for example,

In the figure above, the channel is full and SendQ has a waiting sender (say G8, sending data 800), when the receive operation is performed, the third condition above occurs.

If we were to just take the G8 data, then the data would not be first-in, first-out.

Therefore, the correct operation is to read the current recvx (0)buffer value 100, then copy the G8 data 800 to 0, and finally move the recvq position forward, sending sendx equal to recvq. Here, you can think about why?

This completes the core flow of the buffering channel. As shown in figure,

In addition, backstage reply channel has a small PPT prepared by me, you can watch it together.

In addition, there is a small PPT prepared by me in the backstage reply channel of the late night canteen of the public account Wu Qinqiang, you can watch it together.