Concurrency with Goroutines and Channels
Introduction to Goroutines and Channels for achieving concurrency in Go programs
Working with Channels: Introduction
Channels are a fundamental concurrency primitive in Go, allowing goroutines to communicate and synchronize with each other. They provide a safe and elegant way to pass data between concurrently executing functions, preventing race conditions and other common concurrency problems. This document provides an introduction to Go channels, covering their purpose, declaration, initialization, and basic send/receive operations.
Introduction to Channels
Purpose of Channels
Channels serve several key purposes in concurrent Go programs:
- Communication: They enable goroutines to exchange data with each other.
- Synchronization: They allow goroutines to wait for specific events or data from other goroutines.
- Data Sharing: They provide a controlled way to share data between goroutines, preventing data corruption and race conditions.
Declaring and Initializing Channels
Channels are declared using the chan
keyword, followed by the type of data that the channel will carry. They must be initialized using the make
function before they can be used.
Declaration
var ch chan int // Declares a channel that can carry integers
var strChan chan string // Declares a channel that can carry strings
Initialization
ch := make(chan int) // Creates an unbuffered channel of integers
strChan := make(chan string) // Creates an unbuffered channel of strings
//Buffered channels
bufferedChan := make(chan int, 10) //creates a buffered channel of int with a capacity of 10
Unbuffered Channels: Unbuffered channels (created with make(chan T)
) require both a sender and a receiver to be ready at the same time for the communication to happen. The send operation blocks until a receiver is ready, and the receive operation blocks until a sender is ready. This is synchronous communication. Buffered Channels: Buffered channels (created with make(chan T, capacity)
) can hold a certain number of values without requiring an immediate receiver. The sender can send values to the channel as long as there is space available in the buffer. Once the buffer is full, the sender blocks until a receiver retrieves a value. This provides some degree of asynchronicity.
Basic Send/Receive Operations
Channels use the <-
operator for sending and receiving data.
Send Operation
ch <- 10 // Sends the integer value 10 to the channel ch
strChan <- "Hello" //Sends the string "Hello" to channel strChan
The send operation ch <- value
blocks until another goroutine is ready to receive the value from the channel, unless the channel is buffered and has available space.
Receive Operation
value := <-ch // Receives a value from the channel ch and assigns it to the variable value
message := <-strChan //Receives a value from the channel strChan and assigns it to the variable message
The receive operation <-ch
blocks until another goroutine sends a value to the channel, unless the channel is closed.
Send and Receive Example
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
ch <- 10 // Send 10 to the channel
fmt.Println("Sent 10 to channel")
}()
value := <-ch // Receive from the channel
fmt.Println("Received:", value)
}
In this example, a goroutine is launched that sends the integer 10 to the channel ch
. The main goroutine then receives the value from the channel and prints it to the console. Because the channel is unbuffered, the send operation in the goroutine blocks until the main goroutine is ready to receive.
Closing Channels
Channels can be closed using the close
function. Closing a channel signals that no more values will be sent on it.
close(ch)
It is important to note that only the sender should close a channel, never the receiver. Receiving from a closed channel returns the zero value of the channel's type without blocking, along with a second boolean value indicating whether the channel is open or closed.
value, ok := <-ch
if !ok {
// Channel is closed
fmt.Println("Channel is closed")
} else {
fmt.Println("Received:", value)
}
Closing channels is particularly useful when using range
to iterate over a channel. The loop terminates automatically when the channel is closed.
package main
import "fmt"
func main() {
ch := make(chan int, 5) // Buffered channel
for i := 0; i < 5; i++ {
ch <- i
}
close(ch) // Crucial: signal that no more values will be sent
for value := range ch {
fmt.Println("Received:", value)
}
fmt.Println("Done")
}