Fundamentals 25 min read

Understanding Go Channels: Concepts, Implementation, and Practical Usage

This article explains what Go channels are, how they are implemented in the runtime, their data structures, creation, sending, receiving, closing semantics, common pitfalls, and includes detailed code examples to illustrate channel behavior in concurrent programs.

Xueersi Online School Tech Team
Xueersi Online School Tech Team
Xueersi Online School Tech Team
Understanding Go Channels: Concepts, Implementation, and Practical Usage

1. What is a channel

A channel is Go's language‑level communication mechanism between goroutines, allowing them to pass messages safely without sharing memory directly.

It embodies the principle "Do not communicate by sharing memory; instead, share memory by communicating."

2. Channel implementation

2.1 Introduction

Two simple examples demonstrate basic channel usage: creating a non‑buffered channel, launching a goroutine to receive, and sending values.

package main
import "fmt"
func goroutineA(a <-chan int) {
    for {
        select {
        case val := <-a:
            fmt.Println(val)
        }
    }
}
func main() {
    ch := make(chan int)
    go goroutineA(ch)
    ch <- 3
    ch <- 5
}

This shows a non‑buffered channel where sending blocks until a receiver is ready.

Non‑buffered vs buffered channels:

ch := make(chan int)          // non‑buffered
ch := make(chan int, 1024)   // buffered

Attempting to receive from a channel with no sender results in a deadlock error.

2.2 Data structures

The runtime representation is defined in runtime/chan.go :

type hchan struct {
    qcount   uint          // total data in the queue
    dataqsiz uint          // size of the circular queue
    buf      unsafe.Pointer // points to underlying array (buffered only)
    elemsize uint16        // size of each element
    closed   uint32        // closed flag
    elemtype *_type       // element type
    sendx    uint          // send index
    recvx    uint          // receive index
    recvq    waitq         // waiting receivers
    sendq    waitq         // waiting senders
    lock     mutex         // protects all fields
}

type waitq struct {
    first *sudog
    last  *sudog
}

For a buffered channel of capacity 5 with int elements, the internal circular buffer holds the values.

2.3 Creation

Channels are created with the built‑in make function. During compilation, make(chan int, 10) is transformed into an OMAKECHAN node, which later becomes a call to makechan or makechan64 in the runtime.

func makechan(t *chantype, size int) *hchan {
    // ... validation and memory allocation ...
    // allocate hchan and optional buffer
    // initialize fields (elemsize, elemtype, dataqsiz, etc.)
    return c
}

The function handles three cases: zero‑size buffer (non‑buffered), elements without pointers, and elements containing pointers.

2.4 Receiving

Receiving can be performed as i <- ch or i, ok := <-ch . Both compile to an ORECV node; the latter becomes OAS2RECV to also return the closed flag.

func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
    // step1: nil channel handling
    // step2: fast‑path non‑blocking checks
    // step3: lock and closed check
    // step4: if waiting sender exists, receive directly
    // step5: if buffer has data, dequeue from buffer
    // step6: block if necessary
}

The implementation walks through six logical steps, handling nil channels, fast non‑blocking cases, closed channels, waiting senders, buffered data, and blocking when needed.

2.5 Sending

Sending follows a similar multi‑step process:

func chansend(c *hchan, ep unsafe.Pointer, block bool) bool {
    // step1: nil channel handling
    // step2: fast‑path non‑blocking checks
    // step3: lock and closed check (panic on closed)
    // step4: if waiting receiver exists, deliver directly
    // step5: if buffer space available, enqueue
    // step6: block if buffer full
}

Direct delivery to a waiting receiver bypasses the buffer, while buffered sends copy the value into the circular queue.

2.6 Closing

Closing a channel invokes closechan :

func closechan(c *hchan) {
    if c == nil { panic("close of nil channel") }
    lock(&c.lock)
    if c.closed != 0 { panic("close of closed channel") }
    c.closed = 1
    // wake all waiting receivers and senders
    unlock(&c.lock)
}

All waiting receivers are released with zero values; waiting senders panic.

3. Questions and Answers

What operations cause a panic? Sending on a closed channel, closing a nil channel, or closing an already closed channel.

Are channels concurrency‑safe? Yes, the runtime synchronizes access.

Can you read from a closed channel? Yes, you can still receive remaining buffered values; after the buffer is empty, receives yield the zero value of the element type.

What is the essence of sending/receiving? A value copy between the sender and receiver (or buffer).

4. Example Programs

Simple send/receive example:

package main
import (
    "fmt"
    "time"
)
func print(u <-chan int) {
    time.Sleep(2 * time.Second)
    fmt.Println("print int", <-u)
}
func main() {
    c := make(chan int, 5)
    a := 0
    c <- a
    fmt.Println(a)
    a = 1
    go print(c)
    time.Sleep(5 * time.Second)
    fmt.Println(a)
}

Pointer example demonstrates that the channel stores a copy of the pointer value, not the variable itself:

package main
import (
    "fmt"
    "time"
)

type people struct { name string }
var u = people{name: "A"}

func printPeople(u <-chan *people) {
    time.Sleep(2 * time.Second)
    fmt.Println("printPeople", <-u)
}

func main() {
    c := make(chan *people, 5)
    a := &u
    c <- a
    fmt.Println(a)
    a = &people{name: "B"}
    go printPeople(c)
    time.Sleep(5 * time.Second)
    fmt.Println(a)
}

Output shows that the channel retains the original address ( &{A} ) even after the variable is reassigned.

5. References

《深度解密Go语言之channel》 – Blog园, author Stefno

《浅谈Go语言编译原理》 – Draveness's Blog

《Concurrency in Go》

concurrencyGocommunicationgoroutinechannelBuffer
Xueersi Online School Tech Team
Written by

Xueersi Online School Tech Team

The Xueersi Online School Tech Team, dedicated to innovating and promoting internet education technology.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.