Skip to main content
โšก Calmops

Channels: Communication Between Goroutines

Channels: Communication Between Goroutines

Channels are Go’s primary mechanism for safe communication between goroutines. They enable synchronization and data exchange.

Channel Basics

Creating Channels

package main

import "fmt"

func main() {
    // Unbuffered channel
    ch := make(chan int)

    // Buffered channel
    buffered := make(chan int, 5)

    // Channel of strings
    strCh := make(chan string)

    fmt.Println(ch, buffered, strCh)
}

Sending and Receiving

package main

import "fmt"

func main() {
    ch := make(chan int)

    // Send in goroutine
    go func() {
        ch <- 42
    }()

    // Receive
    value := <-ch
    fmt.Println(value)  // 42
}

Unbuffered vs Buffered Channels

Unbuffered Channels

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan int)

    go func() {
        fmt.Println("Sending...")
        ch <- 42
        fmt.Println("Sent")
    }()

    time.Sleep(1 * time.Second)
    fmt.Println("Receiving...")
    value := <-ch
    fmt.Println("Received:", value)
}

Buffered Channels

package main

import "fmt"

func main() {
    // Buffered channel with capacity 2
    ch := make(chan int, 2)

    // Can send without blocking
    ch <- 1
    ch <- 2

    // Receive
    fmt.Println(<-ch)  // 1
    fmt.Println(<-ch)  // 2
}

Channel Operations

Closing Channels

package main

import "fmt"

func main() {
    ch := make(chan int, 3)

    ch <- 1
    ch <- 2
    ch <- 3
    close(ch)

    // Receive from closed channel
    for value := range ch {
        fmt.Println(value)
    }
}

Checking if Channel is Closed

package main

import "fmt"

func main() {
    ch := make(chan int, 2)

    ch <- 1
    ch <- 2
    close(ch)

    // Check if channel is closed
    value, ok := <-ch
    fmt.Println(value, ok)  // 0 false

    value, ok = <-ch
    fmt.Println(value, ok)  // 0 false
}

Select Statement

Basic Select

package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        time.Sleep(1 * time.Second)
        ch1 <- "one"
    }()

    go func() {
        time.Sleep(2 * time.Second)
        ch2 <- "two"
    }()

    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-ch1:
            fmt.Println("Received:", msg1)
        case msg2 := <-ch2:
            fmt.Println("Received:", msg2)
        }
    }
}

Select with Default

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan int)

    select {
    case value := <-ch:
        fmt.Println("Received:", value)
    default:
        fmt.Println("No value available")
    }

    // Non-blocking send
    select {
    case ch <- 42:
        fmt.Println("Sent")
    default:
        fmt.Println("Channel full")
    }
}

Select with Timeout

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan int)

    select {
    case value := <-ch:
        fmt.Println("Received:", value)
    case <-time.After(1 * time.Second):
        fmt.Println("Timeout")
    }
}

Channel Patterns

Pipeline Pattern

package main

import "fmt"

func generate(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        for _, n := range nums {
            out <- n
        }
        close(out)
    }()
    return out
}

func square(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        for n := range in {
            out <- n * n
        }
        close(out)
    }()
    return out
}

func main() {
    nums := generate(1, 2, 3, 4, 5)
    squares := square(nums)

    for n := range squares {
        fmt.Println(n)
    }
}

Fan-Out/Fan-In Pattern

package main

import (
    "fmt"
    "sync"
)

func worker(id int, jobs <-chan int, results chan<- int) {
    for job := range jobs {
        fmt.Printf("Worker %d processing job %d\n", id, job)
        results <- job * 2
    }
}

func main() {
    jobs := make(chan int, 10)
    results := make(chan int, 10)

    // Fan-out: start workers
    for i := 1; i <= 3; i++ {
        go worker(i, jobs, results)
    }

    // Send jobs
    for j := 1; j <= 10; j++ {
        jobs <- j
    }
    close(jobs)

    // Fan-in: collect results
    for i := 0; i < 10; i++ {
        fmt.Println(<-results)
    }
}

Directional Channels

Send-Only Channels

package main

import "fmt"

func send(ch chan<- int) {
    ch <- 42
}

func main() {
    ch := make(chan int)

    go send(ch)

    value := <-ch
    fmt.Println(value)
}

Receive-Only Channels

package main

import "fmt"

func receive(ch <-chan int) {
    value := <-ch
    fmt.Println(value)
}

func main() {
    ch := make(chan int)

    go func() {
        ch <- 42
    }()

    receive(ch)
}

Best Practices

โœ… Good: Close Channels Properly

// DO: Close channels from sender side
ch := make(chan int)
go func() {
    ch <- 1
    ch <- 2
    close(ch)
}()

for value := range ch {
    fmt.Println(value)
}

โŒ Bad: Receive from Closed Channel

// DON'T: Receive from closed channel
ch := make(chan int)
close(ch)
value := <-ch  // Panic!

โœ… Good: Use Directional Channels

// DO: Use directional channels in function signatures
func send(ch chan<- int) { ch <- 42 }
func receive(ch <-chan int) { <-ch }

โœ… Good: Use Select for Timeouts

// DO: Use select with timeout
select {
case value := <-ch:
    fmt.Println(value)
case <-time.After(1 * time.Second):
    fmt.Println("Timeout")
}

Summary

Channels provide:

  1. Safe communication between goroutines
  2. Synchronization primitives
  3. Buffering for decoupling
  4. Select statement for multiplexing
  5. Directional channels for type safety

These features enable safe concurrent programming in Go.

Comments