📢 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.
}
}
}
|