Concurrency ใน Go ตอนที่ 2: Channel
Source: Dev.to
Channel
“Do not communicate by sharing memory; instead, share memory by communicating.”
In Go, goroutines usually exchange data through channels rather than mutating shared variables.
- Sending a value:
c <- v - Receiving a value:
v := <-c
c is a typed channel; its element type is fixed when the channel is created.
func multiply(v1, v2 int, c chan int) {
c <- v1 * v2
}
func main() {
c := make(chan int)
go multiply(1, 2, c)
go multiply(3, 4, c)
a := <-c
b := <-c
fmt.Println(a * b) // prints 24
}
The channel’s built‑in blocking semantics make an explicit WaitGroup unnecessary: a send blocks until a receiver is ready, and a receive blocks until a value is available.
Using a channel for simple synchronization
A channel can act as a one‑off signal, similar to a WaitGroup.
ready := make(chan bool)
go func() {
// do some work...
ready <- true // signal completion
}()
<-ready // wait until the goroutine signals
The received value is often ignored; the program merely waits for the send to occur.
Receiving a stream with for range
When a producer closes a channel, the receiver can iterate until the channel is closed.
func producer(c chan int) {
for i := 1; i <= 5; i++ {
fmt.Println("Sending", i)
c <- i
time.Sleep(500 * time.Millisecond)
}
close(c)
}
func main() {
c := make(chan int)
go producer(c)
for v := range c {
fmt.Print("Receiving ", v)
}
fmt.Println("\nDone")
}
Important: The sender should close the channel when no more values will be sent. Closing from the receiver side can cause a panic if the sender is still active.
Buffered vs Unbuffered Channels
By default a channel has capacity 0 (unbuffered) and synchronizes each send with a corresponding receive.
A buffered channel allows a limited number of values to be queued without blocking the sender:
c := make(chan int, 3) // capacity 3
- The first three sends succeed immediately.
- The fourth send blocks until a receiver reads a value.
- Receives block only when the buffer is empty.
Buffered channels are useful when producers and consumers run at different speeds, smoothing temporary mismatches.
Directional Channels
When a channel is passed as a function parameter, its direction can be restricted:
- Send‑only:
func producer(c chan<- int) - Receive‑only:
func consumer(c <-chan int)
This makes the intended use explicit and helps the compiler catch misuse.
Select Statement
select works like a switch for channel operations. It blocks until one of its cases can proceed.
Basic usage
func main() {
c1 := make(chan string)
c2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
c1 <- "A"
}()
go func() {
time.Sleep(2 * time.Second)
c2 <- "B"
}()
select {
case res := <-c1:
fmt.Println("Received from c1:", res)
case res := <-c2:
fmt.Println("Received from c2:", res)
}
}
The program prints the value from the channel that becomes ready first (c1 in this example).
Selecting between send and receive
select {
case a := <-inChan:
fmt.Println("Received:", a)
case outChan <- b:
fmt.Println("Sent:", b)
}
Common patterns
Timeout
select {
case res := <-c1:
fmt.Println("Received:", res)
case <-time.After(5 * time.Second):
fmt.Println("Timeout")
}
Non‑blocking default
select {
case res := <-c1:
fmt.Println("Received:", res)
default:
fmt.Println("No message")
}
Abort signal
select {
case res := <-c1:
fmt.Println("Received:", res)
case <-abortChan:
fmt.Println("Abort")
return
}