Channels in Go are a mechanism for communicating between goroutines by sending and receiving values.
Declare:
ch := make(chan int)
The type of a channel specifies what kind of data it can carry.
Send:
ch <- 10
Receive:
value := <-ch
Close:
close(ch)
Closing a channel signals to the receiving goroutine that no more values will be sent. It is important to close a channel when you are done sending values to help avoid deadlocks.
Channel synchronization ensures that communication between goroutines is properly coordinated, guaranteeing that data is not lost and that goroutines wait for each other when necessary to maintain the correct order and timing of operations.
Send Operation: When a goroutine sends a value to a channel, it blocks until another goroutine is ready to receive that value.
Receive Operation: When a goroutine attempts to receive a value from a channel, it blocks until a value becomes available.
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
Here, we do not need any additional mechanism in the main function to wait for the goroutine to finish. The <-ch operation in main blocks until a value is available to receive from the channel. This blocking behavior synchronizes the main function with the expensiveFunc goroutine. Each iteration of the loop in main waits for a corresponding send operation from expensiveFunc. Also, we are not required to use a loop here. We could call fmt.Println(<-ch) directly 4 times in sequence, and it would produce the same result.
In the example above, we did not strictly need to close the channel because main only catches 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 essentially performs a msg := <-ch operation under the hood, which is where the blocking occurs.
range doesn't know how many values a channel will receive, it can be infinite. To stop the loop, we must explicitly close the channel to indicate that no more values are coming.
The select statement in Go is a control structure that allows you to work with multiple channels simultaneously. It is similar to a switch statement but is specifically designed for channel operations.
The select statement listens to multiple channels.
It executes the first case that is ready to proceed.
If multiple cases are ready, Go randomly selects one to execute.
If there is no default case, it blocks until a case becomes ready.
If a default case is present and no other cases are ready, it executes the default case immediately without blocking.
In Go, channels can be restricted to Read-Only or Write-Only. This helps define clear communication patterns between goroutines and improves code safety and clarity.
Read-Only (<-chan type): A read-only channel can only be used to receive values. You cannot send values into a read-only channel.
Write-Only (chan<- type): A write-only channel can only be used to send values. You cannot receive values from a write-only channel.
The "Done Channel" pattern is a technique used to signal the completion or termination of a goroutine. It involves using a separate helper channel to indicate when a goroutine should stop its execution.
Without a Done Channel:
func myFunc() { for { fmt.Println("MyFunc") }}func main() { go myFunc() time.Sleep(1 * time.Hour)}
The myFunc goroutine will print "MyFunc" continuously until the main program exits.
The myFunc goroutine will print "MyFunc" 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.