Go Channels

Communication pipes between goroutines

📡 What are Channels?

Channels are communication pipes that allow goroutines to send and receive values safely. They provide a way to synchronize goroutines and share data without explicit locks or condition variables.


// Basic channel example
package main

import "fmt"

func main() {
    // Create a channel
    ch := make(chan string)
    
    // Send data in a goroutine
    go func() {
        ch <- "Hello from channel!"
    }()
    
    // Receive data
    message := <-ch
    fmt.Println(message)
}
                                    

Channel Types

🔄

Unbuffered

Synchronous communication

ch := make(chan int)
ch <- 42  // Blocks until received
value := <-ch
📦

Buffered

Asynchronous with capacity

ch := make(chan int, 3)
ch <- 1  // Won't block
ch <- 2  // Won't block
ch <- 3  // Won't block
📤

Send-only

Can only send values

func sender(ch chan<- int) {
    ch <- 42
    // Can't receive: <-ch
}
📥

Receive-only

Can only receive values

func receiver(ch <-chan int) {
    value := <-ch
    // Can't send: ch <- 42
}

🔹 Creating and Using Channels

Channels are created with make() and used with <- operator:

package main

import (
    "fmt"
    "time"
)

func main() {
    // Create unbuffered channel
    messages := make(chan string)
    
    // Send data in goroutine
    go func() {
        time.Sleep(1 * time.Second)
        messages <- "Hello"
        messages <- "World"
        close(messages) // Close channel when done
    }()
    
    // Receive data
    for msg := range messages {
        fmt.Println("Received:", msg)
    }
    
    fmt.Println("Channel closed")
}

Output:

Received: Hello
Received: World
Channel closed

🔹 Buffered Channels

Buffered channels can hold multiple values without blocking:

package main

import "fmt"

func main() {
    // Create buffered channel with capacity 3
    ch := make(chan int, 3)
    
    // Send values (won't block)
    ch <- 1
    ch <- 2
    ch <- 3
    
    fmt.Printf("Channel length: %d\n", len(ch))
    fmt.Printf("Channel capacity: %d\n", cap(ch))
    
    // Receive values
    fmt.Println("Received:", <-ch)
    fmt.Println("Received:", <-ch)
    fmt.Println("Received:", <-ch)
}

Output:

Channel length: 3
Channel capacity: 3
Received: 1
Received: 2
Received: 3

🔹 Channel Directions

Restrict channels to send-only or receive-only for better design:

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan string)
    
    go sender(ch)
    receiver(ch)
}

// Send-only channel parameter
func sender(ch chan<- string) {
    for i := 1; i <= 3; i++ {
        ch <- fmt.Sprintf("Message %d", i)
        time.Sleep(500 * time.Millisecond)
    }
    close(ch)
}

// Receive-only channel parameter
func receiver(ch <-chan string) {
    for msg := range ch {
        fmt.Println("Got:", msg)
    }
}

Output:

Got: Message 1
Got: Message 2
Got: Message 3

🔹 Worker Pool Pattern

Use channels to implement a worker pool for concurrent processing:

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    const numWorkers = 3
    const numJobs = 9
    
    jobs := make(chan int, numJobs)
    results := make(chan int, numJobs)
    
    // Start workers
    var wg sync.WaitGroup
    for w := 1; w <= numWorkers; w++ {
        wg.Add(1)
        go worker(w, jobs, results, &wg)
    }
    
    // Send jobs
    for j := 1; j <= numJobs; j++ {
        jobs <- j
    }
    close(jobs)
    
    // Wait for workers to finish
    go func() {
        wg.Wait()
        close(results)
    }()
    
    // Collect results
    for result := range results {
        fmt.Printf("Result: %d\n", result)
    }
}

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)
        time.Sleep(500 * time.Millisecond)
        results <- job * 2
    }
}

🧠 Test Your Knowledge

What operator is used to send data to a channel?