Practical Channel Patterns
Channel Design Patterns
Channels in Golang provide powerful synchronization and communication mechanisms. This section explores practical patterns for effective concurrent programming.
Common Channel Patterns
Pattern |
Description |
Use Case |
Worker Pool |
Distribute tasks across multiple workers |
Parallel processing |
Fan-Out/Fan-In |
Spread work and collect results |
Concurrent computation |
Cancellation |
Graceful goroutine termination |
Resource management |
Timeout |
Prevent indefinite blocking |
Preventing deadlocks |
Worker Pool Pattern
func workerPool(jobs <-chan int, results chan<- int, workerCount int) {
var wg sync.WaitGroup
for i := 0; i < workerCount; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobs {
results <- processJob(job)
}
}()
}
wg.Wait()
close(results)
}
func processJob(job int) int {
return job * 2
}
Worker Pool Workflow
graph LR
A[Job Queue] -->|Distribute| B[Worker 1]
A -->|Tasks| C[Worker 2]
A -->|Parallel| D[Worker 3]
B --> E[Result Channel]
C --> E
D --> E
Fan-Out/Fan-In Pattern
func fanOutFanIn(input <-chan int, workerCount int) <-chan int {
results := make(chan int, workerCount)
var wg sync.WaitGroup
// Fan-out
for i := 0; i < workerCount; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for num := range input {
results <- num * num
}
}()
}
// Fan-in
go func() {
wg.Wait()
close(results)
}()
return results
}
Cancellation Pattern
func cancelableOperation(ctx context.Context, data <-chan int) <-chan struct{} {
done := make(chan struct{})
go func() {
defer close(done)
for {
select {
case <-ctx.Done():
return
case _, ok := <-data:
if !ok {
return
}
// Process data
}
}
}()
return done
}
Timeout Pattern
func timeoutOperation(ch <-chan int, timeout time.Duration) (int, bool) {
select {
case value := <-ch:
return value, true
case <-time.After(timeout):
return 0, false
}
}
Advanced Channel Synchronization
Semaphore with Channels
type Semaphore struct {
sem chan struct{}
}
func NewSemaphore(max int) *Semaphore {
return &Semaphore{
sem: make(chan struct{}, max),
}
}
func (s *Semaphore) Acquire() {
s.sem <- struct{}{}
}
func (s *Semaphore) Release() {
<-s.sem
}
Best Practices for LabEx Developers
- Use channels for communication, not shared memory
- Prefer simple patterns over complex synchronization
- Always consider goroutine lifecycle
- Use context for cancellation and timeouts
- Minimize channel communication overhead
- Use buffered channels when appropriate
- Avoid excessive goroutine creation
- Implement proper error handling
Error Handling in Channel Patterns
func safeChannelOperation(ch <-chan int) (int, error) {
select {
case value, ok := <-ch:
if !ok {
return 0, errors.New("channel closed")
}
return value, nil
case <-time.After(5 * time.Second):
return 0, errors.New("operation timeout")
}
}