In Go, channels are used to synchronise between multiple goroutines. They are essentially unidirectional, limited-sized pipes that carry typed messages. Data ownership is transferred from sender to receiver. Additionally, Go’s garbage collector ensures we don’t have to worry about which goroutine in a concurrent API must free memory.
Channels are Go’s preferred synchronisation method, even though the standard library still provides low-level synchronisation methods like mutexes, semaphores, and atomics. As usual, if state cannot be easily moved between threads (like large data structures), it’s better to use shared state and locks.
Programming
Channels must be initialised with make.
ch := make(chan int, 100)The size of the channel is optional. Including it makes it a buffered channel.
We send and receive from channels with the arrow operator:
// sending
ch <- 1
ch <- 2
// receiving
fmt.Println(<-ch)
fmt.Println(<-ch)Sends will block if the buffer is full, and receives will block if the buffer is empty.
If we want to use channels as a signalling mechanism, where we don’t care about what the data is (like a semaphore with a capacity of 1), we can use the struct{} type. This doesn’t allocate any memory and is the smallest available type in Go.1
sema := make(chan struct{}, 1)
sema <- struct{}{}
select {
case <-sema:
go func() {
defer func() {
sema <- struct{}{}
}
}
default:
// couldn't acquire semaphore! skipping
}Footnotes
-
From this Stack Overflow answer. ↩