漫行记Wandering
Journal 2,054 字 7 分钟 Golang

Golang Channel - Part 2

In the previous post, we discussed the basics of Go channels and how they facilitate communication between goroutines. In this post, we will delve deeper into the internal workings of Go channels, specifically focusing on the concept of “sudog” and how it plays a crucial role in channel operations.

Let’s recap the hchan structure that represents a channel in Go’s runtime:

type hchan struct {
    qcount   uint      // number of elements in queue
    dataqsiz uint      // buffer capacity
    buf      unsafe.Pointer // circular buffer
    elemsize uint16    // size of each element
    closed   uint32    // is channel closed?
    elemtype *_type    // element type (for reflection)

    sendx    uint      // send index (next slot to write)
    recvx    uint      // recv index (next slot to read)

    recvq    waitq     // list of waiting receivers
    sendq    waitq     // list of waiting senders

    lock     mutex     // protects all fields
}

sudog

A sudog (sudo goroutine) is a runtime data structure in Go that represents a goroutine’s waiting state on a synchronization primitive.

sudog in channel

So how is the waitq implemented? It is a linked list of sudog structures, The sudog structure represents a goroutine that is waiting to send or receive on a channel.

type waitq struct {
    first *sudog
    last  *sudog
}

type sudog struct {
    g          *g        // goroutine
    next       *sudog    // next sudog
    prev       *sudog    // previous sudog
    elem       unsafe.Pointer // pointer to element
    c          *hchan    // channel
    // ... other fields ...
}

So for the channel, we may see the sendq and recvq as below:

sendq
┌─────────┐    ┌─────────┐    ┌─────────┐
│ sudog1  │───→│ sudog2  │───→│ sudog3  │
│ g: G1   │    │ g: G2   │    │ g: G3   │
│ elem: * │    │ elem: * │    │ elem: *
└─────────┘    └─────────┘    └─────────┘

recvq 
┌─────────┐    ┌─────────┐
│ sudog4  │───→│ sudog5  │
│ g: G4   │    │ g: G5   │
│ elem: * │    │ elem: *
└─────────┘    └─────────┘

sudog in other places

Actually, sudog is not only used in channels, but also in other places like sync.Cond, WaitGroup, Mutex and so on. So we can describe sudog as a general structure to represent a goroutine that is waiting for some event to happen.

sudog = {
    goroutine: waiting goroutine,
    sync_object: the object the goroutine is waiting on (channel, mutex, cond, etc.),
    operation: send/receive/lock/wait,
    context: additional context (like the element to send/receive),
}

The relationship between goroutine and sudog

One goroutine can have multiple sudogs, but one sudog only belongs to one goroutine.

// In a select statement
select {
case <-ch1:  // sudog1: same goroutine waiting on ch1
case <-ch2:  // sudog2: same goroutine waiting on ch2  
case <-ch3:  // sudog3: same goroutine waiting on ch3
}

 ---> 
 
sudog1 ────→ channel1.recvq
┌─────────────┐
│   sudog1    │
│ c: ─────────┼───→ channel1
│ elem: ──────┼───→ &v1
│ g: ─────────┼───→ current_goroutine
└─────────────┘

sudog2 ────→ channel2.recvq  
┌─────────────┐
│   sudog2    │
│ c: ─────────┼───→ channel2
│ elem: ──────┼───→ &v2
│ g: ─────────┼───→ current_goroutine (same one)
└─────────────┘

sudog3 ────→ channel3.sendq
┌─────────────┐
│   sudog3    │
│ c: ─────────┼───→ channel3
│ elem: ──────┼───→ &val
│ g: ─────────┼───→ current_goroutine (same one)
└─────────────┘

Here, one goroutine is waiting on multiple channels, so it has multiple sudogs.

← 随笔