Skip to main content
โšก Calmops

Select Statement and Channel Operations

Select Statement and Channel Operations

The select statement is Go’s mechanism for multiplexing channel operations. It enables handling multiple channels concurrently.

Basic Select

Waiting for Multiple Channels

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 from ch1:", msg1)
        case msg2 := <-ch2:
            fmt.Println("Received from ch2:", 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")
    }
}

Timeouts

Using time.After

package main

import (
    "fmt"
    "time"
)

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

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

Timeout with Goroutine

package main

import (
    "fmt"
    "time"
)

func fetchData(ch chan string) {
    time.Sleep(2 * time.Second)
    ch <- "Data received"
}

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

    go fetchData(ch)

    select {
    case data := <-ch:
        fmt.Println(data)
    case <-time.After(1 * time.Second):
        fmt.Println("Request timeout")
    }
}

Ticker

Using time.Tick

package main

import (
    "fmt"
    "time"
)

func main() {
    ticker := time.NewTicker(1 * time.Second)
    defer ticker.Stop()

    for i := 0; i < 3; i++ {
        select {
        case <-ticker.C:
            fmt.Println("Tick", i+1)
        }
    }
}

Ticker with Timeout

package main

import (
    "fmt"
    "time"
)

func main() {
    ticker := time.NewTicker(500 * time.Millisecond)
    defer ticker.Stop()

    timeout := time.After(2 * time.Second)

    for {
        select {
        case <-ticker.C:
            fmt.Println("Tick")
        case <-timeout:
            fmt.Println("Timeout")
            return
        }
    }
}

Advanced Patterns

Fan-In Pattern

package main

import (
    "fmt"
    "time"
)

func producer(id int, ch chan string) {
    for i := 0; i < 3; i++ {
        time.Sleep(time.Duration(id) * 100 * time.Millisecond)
        ch <- fmt.Sprintf("Producer %d: message %d", id, i)
    }
}

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

    go producer(1, ch1)
    go producer(2, ch2)

    for i := 0; i < 6; i++ {
        select {
        case msg := <-ch1:
            fmt.Println(msg)
        case msg := <-ch2:
            fmt.Println(msg)
        }
    }
}

Worker Pool with Select

package main

import (
    "fmt"
    "time"
)

func worker(id int, jobs <-chan int, results chan<- string) {
    for job := range jobs {
        fmt.Printf("Worker %d processing job %d\n", id, job)
        time.Sleep(100 * time.Millisecond)
        results <- fmt.Sprintf("Worker %d completed job %d", id, job)
    }
}

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

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

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

    // Collect results
    for i := 0; i < 5; i++ {
        fmt.Println(<-results)
    }
}

Select with Nil Channels

Disabling Channels

package main

import (
    "fmt"
    "time"
)

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

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

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

    var ch1Open, ch2Open bool = true, true

    for ch1Open || ch2Open {
        select {
        case msg, ok := <-ch1:
            if !ok {
                ch1 = nil  // Disable ch1
                ch1Open = false
            } else {
                fmt.Println("ch1:", msg)
            }
        case msg, ok := <-ch2:
            if !ok {
                ch2 = nil  // Disable ch2
                ch2Open = false
            } else {
                fmt.Println("ch2:", msg)
            }
        }
    }
}

Best Practices

โœ… Good: Use Select for Multiplexing

// DO: Use select for multiple channels
select {
case msg := <-ch1:
    fmt.Println(msg)
case msg := <-ch2:
    fmt.Println(msg)
case <-time.After(1 * time.Second):
    fmt.Println("Timeout")
}

โœ… Good: Always Handle Timeouts

// DO: Include timeout in select
select {
case result := <-ch:
    fmt.Println(result)
case <-time.After(5 * time.Second):
    fmt.Println("Operation timed out")
}

โœ… Good: Use Default for Non-Blocking

// DO: Use default for non-blocking operations
select {
case msg := <-ch:
    fmt.Println(msg)
default:
    fmt.Println("No message available")
}

โŒ Bad: Blocking Without Timeout

// DON'T: Block indefinitely
msg := <-ch  // Could hang forever

Summary

The select statement provides:

  1. Multiplexing multiple channels
  2. Timeouts with time.After
  3. Non-blocking operations with default
  4. Ticker support for periodic tasks
  5. Advanced patterns like fan-in/fan-out

These features enable sophisticated concurrent patterns in Go.

Comments