Channels and Safety
Understanding Channels in Go
Channels are a fundamental mechanism in Go for communication and synchronization between goroutines, providing a safe and efficient way to share data.
Channel Basics
Channel Types and Creation
package main
import "fmt"
func main() {
// Unbuffered channel
unbufferedChan := make(chan int)
// Buffered channel
bufferedChan := make(chan string, 5)
}
Channel Communication Flow
graph LR
A[Goroutine 1] -->|Send| B[Channel]
B -->|Receive| C[Goroutine 2]
Channel Types and Characteristics
Channel Type |
Description |
Use Case |
Unbuffered |
Synchronous communication |
Strict coordination |
Buffered |
Asynchronous communication |
Decoupled processing |
Directional |
Send-only or receive-only |
Restricted access |
Sending and Receiving Data
package main
import (
"fmt"
"time"
)
func producer(ch chan<- int) {
for i := 0; i < 5; i++ {
ch <- i
time.Sleep(time.Millisecond * 500)
}
close(ch)
}
func consumer(ch <-chan int) {
for num := range ch {
fmt.Println("Received:", num)
}
}
func main() {
ch := make(chan int)
go producer(ch)
go consumer(ch)
time.Sleep(3 * time.Second)
}
Advanced Channel Patterns
Select Statement
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 <- "First channel"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "Second channel"
}()
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
case <-time.After(3 * time.Second):
fmt.Println("Timeout")
}
}
Channel Safety Principles
- Always close channels when done
- Prevent goroutine leaks
- Use buffered channels carefully
- Implement proper error handling
Closing Channels
package main
import "fmt"
func main() {
ch := make(chan int, 5)
// Sending values
for i := 0; i < 5; i++ {
ch <- i
}
// Close channel after sending
close(ch)
// Safe iteration
for num := range ch {
fmt.Println(num)
}
}
Concurrency Patterns
Worker Pool
package main
import (
"fmt"
"sync"
)
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for job := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, job)
results <- job * 2
}
}
func main() {
jobs := make(chan int, 100)
results := make(chan int, 100)
var wg sync.WaitGroup
// Create worker pool
for w := 1; w <= 3; w++ {
wg.Add(1)
go worker(w, jobs, results, &wg)
}
// Send jobs
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
wg.Wait()
close(results)
// Collect results
for result := range results {
fmt.Println("Result:", result)
}
}
- Channels have overhead
- Choose between channels and mutexes wisely
- Avoid excessive channel creation
Conclusion
Channels provide a powerful, safe mechanism for concurrent communication in Go. LabEx recommends mastering channel patterns to write efficient, concurrent applications.