Channels in Go provide a way for goroutines to communicate by sending and receiving values.
Declare:
ch := make(chan int)
The channel type defines what kind of data it can carry. It can be any type, including named types.
Send:
ch <- 10
Receive:
value := <-ch
Close:
close(ch)
Channels usually do not need to be closed manually. Closing is only necessary when the receiver must be told there are no more values coming, such as to terminate a range loop.
Channel synchronization coordinates communication between goroutines. It ensures data is not lost and preserves the correct order.
Send operation: Blocks the current goroutine until another goroutine (the other side) is ready to receive.
Receive operation: Blocks the current goroutine until a value is available (from the other side).
Example:
func main() { ch := make(chan string) go expensiveFunc("Hello", ch) fmt.Println("Main") for range 4 { fmt.Println(<-ch) } fmt.Println("End")}func expensiveFunc(text string, ch chan string) { for i := range 4 { time.Sleep(500 * time.Millisecond) ch <- text + " " + fmt.Sprint(i) }}// Main// Hello 0// Hello 1// Hello 2// Hello 3// End
No extra mechanism is required in main to wait for the goroutine. The <-ch operation blocks until a value is available. This behavior synchronizes main with expensiveFunc. Each loop iteration in main waits for a send from expensiveFunc. The loop is used only as an example, calling fmt.Println(<-ch) 4 times back to back would do the same.
In the example above, closing the channel is not required because main receives a fixed number of messages (4) before exiting. However, here is a modified version that requires the channel to be closed explicitly:
func main() { ch := make(chan string) go expensiveFunc("Hello", ch) fmt.Println("Main") for msg := range ch { fmt.Println(msg) } fmt.Println("Done.")}func expensiveFunc(text string, ch chan string) { defer close(ch) for i := range 4 { time.Sleep(500 * time.Millisecond) ch <- text + " " + fmt.Sprint(i) }}// Main// Hello 0// Hello 1// Hello 2// Hello 3// Done.
The for msg := range ch { ... } syntax performs msg := <-ch internally, where blocking occurs.
range does not know how many values a channel will receive, it may be infinite. To stop the loop, close the channel to signal that no more values will be sent.
The select statement is a control structure for working with multiple channels simultaneously. It is similar to switch statement, but designed for channel operations.
select listens on multiple channels.
It executes the first ready case.
If multiple cases are ready, one is chosen randomly.
Without a default, it blocks until a case is ready.
With a default, it executes the default itself immediately if no cases are ready.
The Done Channel pattern signals cancellation or completion in goroutines. It uses a separate shared channel, which is closed to notify them to stop execution.
Without a Done Channel:
func myFunc() { for { fmt.Println("Working...") }}func main() { go myFunc() time.Sleep(1 * time.Hour)}
The myFunc goroutine prints "Working..." continuously until the program exits.
The myFunc goroutine prints "Working..." for 5 seconds and then return, even though the main program continues to run for an hour.
The struct{} type represents an empty struct in Go. It consumes zero bytes of memory, making it ideal for
signaling and control purposes without causing any memory overhead.
The Fan-In pattern combines multiple input channels into a single output channel so one consumer can read from all sources.
func merge(ch1, ch2 <-chan int) <-chan int { out := make(chan int) go func() { for { select { case v := <-ch1: out <- v case v := <-ch2: out <- v } } }() return out}func myFunc(nums ...int) <-chan int { ch := make(chan int) // Business logic: go func() { for _, n := range nums { ch <- n } }() return ch}func main() { a := myFunc(1, 3, 5) b := myFunc(2, 4, 6) result := merge(a, b) for i := 0; i < 6; i++ { fmt.Println(<-result) }}// 1// 3// 2// 5// 4// 6
Output order is not guaranteed because values are received as soon as they arrive from either channel.