Welcome To Golang By Example

Inner working of Channels in Golang

Introduction

The purpose of this article is to give an idea of the inner working of channels. Golang has two  concurrency primitives:

  1. Goroutine – lightweight independent execution to achieve concurrency/parallelism.
  2. Channels – provides synchronization and communication between goroutines.

Channels are goroutine safe and manage communication between goroutine in a FIFO way. A goroutine can block on a channel while doing send or receive of some data and it is the responsibility of the channel to wake up blocked goroutine

Types of Channels

Buffered Channel

Unbuffered Channel

HCHAN struct

Let’s understand what happens internally when you make channels. A channel is internally represented by a hchan struct whose main elements are:

type hchan struct {
    qcount   uint           // total data in the queue
    dataqsiz uint           // size of the circular queue
    buf      unsafe.Pointer // points to an array of dataqsiz elements
    elemsize uint16
    closed   uint32         // denotes weather channel is closed or not
    elemtype *_type         // element type
    sendx    uint           // send index
    recvx    uint           // receive index
    recvq    waitq          // list of recv waiters
    sendq    waitq          // list of send waiters
    lock     mutex
}
     
type waitq struct {
   first *sudog
   last  *sudog
}

The struct sudog main elements are represented as below:

type sudog struct {
   g     *g             //goroutine
   elem  unsafe.Pointer // data element 
   ...
}

Let’s understand what happens during send and receive of a channel

Send on a channel

  1. No receiver/receivers waiting: Unbuffered Channel or the Buffer is Full in case of Buffered Channel.
  2. Receiver/receivers waiting: Unbuffered Channel or the Buffer is empty in case of Buffered Channel
  3. Buffer empty: In case of buffered channel
  4. Channel closed

1. No Receiver/Receivers Waiting:

For below two scenarios the behavior will be the same when there are no receivers waiting.

The goroutine G1 which is trying to send to the channel, its execution is paused and is resumed only after a receive. Let’s see how that happens.

2. Receivers waiting

For below two scenarios the behavior will be the same when there are receivers waiting

Let’s see how that happens.

3. Buffer not full:

Only applicable for buffered channels: Buffer has at least one empty space

4. Channel closed:

Receive From a channel

  1. No sender/senders waiting: Unbuffered Channel or buffer is empty in case of Buffered Channel
  2. Sender/senders waiting: Unbuffered Channel or the Buffer is empty in case of Buffered Channel.
  3. Non-empty Buffer: In case of buffered channel. Channel has at least 1 item.
  4. Channel closed

1. No sender/senders waiting:

For below two scenarios, the behavior will be the same when there are no receivers waiting.

The goroutine G1 which is trying to receive, its execution is paused and is resumed only after a send. Let’s see how that happens.

2. Sender/Senders waiting:

3. Non-Empty Buffer:

Only applicable for buffered channels:

4. Channel closed: