Overview
At a high level a channel is a pointer to the struct with a buffer, two wait queues and a lock
The hchan
Struct
A channel is a pointer to a hchan
struct allocated on the heap:
The main fields are:
buf
a circular FIFO queue storing elements of the channel (sendx
andrecvx
are indexes to head and tail)recvq
a linked list of goroutines represented bysudog
struct waiting for receiving data from the channelsendq
a linked list of goroutines represented bysudog
struct waiting to send data on the channellock
a runtime version of a mutex protecting fields inhchan
andsudog
Enqueueing an element to the buf
causes a copy of the value to be added to the buf
. Dequeuing an element from the buf
causes a copy of the buf
element to be assigned to the variable. It provides channels memory safety guarantees, as the only shared memory is a hchan
protected by a lock
The sudog
Struct
Represents a goroutine (pseudo-goroutine) stored in a wait list:
The main fields are:
g
a pointer to the waiting goroutineelem
an elementg
is waiting to send/receivenext
/prev
pointers to the next/previousg
in a wait listhchan.recvq
/hchan.sendq
Channel Send
Send on a Channel
Actions when G
sends on a non-full and non-empty channel:
- Acquire the
lock
- Enqueue the element to the
buf
. It stores a copy of the element in thebuf
- Release the
lock
Send on a Full Channel
Actions when G
sends on a full channel (blocking call):
- Acquire the
lock
- Create a
sudog
for ourselves with element we are waiting to send and enqueue it to thesendq
. A receiver will use it to resume us in the future - Make a
gopark
call to the scheduler and release thelock
- Scheduler changes
G
state from running to waiting and removes association betweenG
andM
- Scheduler dequeues the runnable
G'
from the run queue and schedules it ontoM
Essentially a user-level context switch, we blocked the G
but not the OS thread M
that started running a G'
Send on an Empty Channel
Actions when G
sends on an empty channel and there is a blocked receiver G'
:
- Acquire the
lock
- Dequeue the
sudog
struct of the waiting receiverG'
from therecvq
- Directly copy the element to the
element
field of the waiting receiverG'
sudog
. An optimization, when the blocked goroutineG'
resumes it doesn’t have to acquire the lock and dequeue the element frombuf
- Make a
goready(G')
call to the scheduler and release thelock
- Scheduler changes
G'
state from waiting to runnable and enqueues it to the run queue. It will be eventually scheduled since it’s on the run queue G
continues executing
Receive from a Channel
Actions when G
receives from a non-empty and non-full channel:
- Acquire the
lock
- Dequeue the element from the
buf
. It copies the element inbuf
to variable in assignment - Release the
lock
Receive from an Empty Channel
Actions when G
receives from an empty channel (blocking call):
- Acquire the
lock
- Create a
sudog
for ourselves with address we are waiting to receive to and enqueue it to therecvq
. A sender will use it to resume us in the future - Make a
gopark
call to the scheduler and release thelock
- Scheduler changes
G
state from running to waiting and removes association betweenG
andM
- Scheduler dequeues the runnable
G'
from the run queue and schedules it ontoM
Receive from a Full Channel
Actions when G
receives from a full channel and there is a blocked sender G'
:
- Acquire the
lock
- Dequeue the element from the
buf
- Dequeue the
sudog
struct of the waiting senderG'
from thesendq
- Enqueue the element
elem
from thesudog
into thebuf
. An optimization, when the blocked goroutineG'
resumes it doesn’t have to acquire the lock to enqueue the element - Make a
goready(G')
call to the scheduler and release thelock
- Scheduler changes
G'
state from waiting to runnable and enqueues it to the run queue. It will be eventually scheduled since it’s on the run queue G
continues executing
Resume in unbuffered/synchronous Channels
Unbuffered channels always work as direct send case
- Receiver waiting → sender directly writes to receiver’s stack fromÂ
sudog
- Sender waiting → receiver directly writes to sender’s stack fromÂ
sudog
Select Statement
- All channels are locked
- AÂ
sudog
 is put in thesendq
/recvq
 queues of all channels - Channels unlocked, all the selecting G is paused
- CAS operation so there is only one winning case
- Resuming mirrors the pause sequence