GoLang Channels

📢 This article was translated by gemini-2.5-flash

Golang Series

Hello GoLang: https://blog.yexca.net/archives/154
GoLang (var and const) 变量与常量: https://blog.yexca.net/archives/155
GoLang (func) 函数: https://blog.yexca.net/archives/156
GoLang (slice and map) 切片: https://blog.yexca.net/archives/160
GoLang (OOP) 面向对象: https://blog.yexca.net/archives/162
GoLang (reflect) 反射: https://blog.yexca.net/archives/204
GoLang (struct tag) 结构体标签: https://blog.yexca.net/archives/205
GoLang (goroutine) go 程: https://blog.yexca.net/archives/206
GoLang (channel) 通道: This article


Goroutines can pass data using channels. Reference-type channels are for communication between multiple goroutines, handling synchronization internally to ensure concurrent safety.

Kinda like RabbitMQ (just my personal analogy for easier learning, they’re actually different things).

Declaring Variables

Channels are reference types. When copied or passed to a function, they refer to the same channel object. The zero value is nil.

Create them using the make() function, for example:

1
2
3
c := make(chan int)
// Add capacity of 3
c := make(chan int, 3)

If the capacity is 0, the channel is unbuffered, blocking reads and writes. If greater than 0, it’s buffered and non-blocking until full, then it blocks.

Use <- to send and receive data:

1
2
3
4
5
6
7
8
// Send data to channel
channel <- 3
// Receive and discard
<-channel // Note no space
// Receive and assign to a variable
x := <-channel
// Receive, assign to a variable, and check if successful (if channel is empty)
data, flag := <-channel

Unbuffered

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import "fmt"

func main() {
    c := make(chan int)
    go func() {
        defer fmt.Println("A.defer")
        c <- 6
        fmt.Println("A running")
    }()

    num := <-c
    fmt.Println("num =", num)

    fmt.Println("main finished")
}

Buffered

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
    "fmt"
    "time"
)

func main() {
    c := make(chan int, 3)

    go func() {
        defer fmt.Println("A finished")
        for i := 0; i < 3; i++ {
            c <- i
            fmt.Println("A goroutine, i =", i, "len =", len(c), "cap =", cap(c))
        }
    }()

    time.Sleep(2 * time.Second)

    for i := 0; i < 3; i++ {
        num := <-c
        fmt.Println("main, num =", num)
    }

    fmt.Println("main finished")
}

Closing Channels

Close them using close():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import "fmt"

func main() {
    c := make(chan int, 5)

    go func() {
        for i := 0; i < 5; i++ {
            c <- i
        }
        // Close
        close(c)
    }()

    for {
        if data, ok := <-c; ok {
            fmt.Println(data)
        } else {
            break
        }
    }

    fmt.Println("Main Finished")
}

Using range

The above main’s for loop can be simplified using range:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import "fmt"

func main() {
    c := make(chan int, 5)

    go func() {
        for i := 0; i < 5; i++ {
            c <- i
        }

        close(c)
    }()

    //for {
    //  if data, ok := <-c; ok {
    //      fmt.Println(data)
    //  } else {
    //      break
    //  }
    //}

    for data := range c {
        fmt.Println(data)
    }
    fmt.Println("Main Finished")
}

Unidirectional Channels

By default, channels are bidirectional (read and write). You can also specify a channel’s direction, making it read-only or write-only.

1
2
3
4
5
var c chan int // Declare a normal bidirectional channel
// c1 is write-only
var c1 chan<- int
// c2 is read-only
var c2 <-chan int

You can convert a bidirectional channel to a unidirectional one, but not the other way around. This means you can define function parameters as unidirectional but pass a bidirectional channel.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//   chan<- // Write-only
func counter(out chan<- int) {
    defer close(out)
    for i := 0; i < 5; i++ {
        out <- i // Will block if the receiver doesn't read
    }
}
 
//   <-chan // Read-only
func printer(in <-chan int) {
    for num := range in {
        fmt.Println(num)
    }
}
 
func main() {
    c := make(chan int) // Bidirectional
 
    go counter(c) // Producer
    printer(c)    // Consumer
 
    fmt.Println("done")
}

select

select can listen for data flow on multiple channels. Its syntax is similar to switch, but each case statement must be an I/O operation.

It’s typically placed inside a for{} block.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import "fmt"

func main() {
    c := make(chan int)
    quit := make(chan int)

    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }

        quit <- 0
    }()

    // main
    x, y := 1, 1
    for {
        select {
        case c <- x:
            tmp := x
            x = y
            y = tmp + y
        case <-quit:
            fmt.Println("quit")
            return
         // A default case is possible, but not needed here.
        }
    }
}