Concurrency in Go

Goroutines

Goroutines are lightweight threads managed by the Go runtime. Use the go keyword to start a new goroutine:

go process(a, b, c)

Arguments are evaluated in the current goroutine, while execution occurs in the new goroutine. Goroutines share the same address space, requiring synchronization for shared memory acess.

package main

import (
    "fmt"
    "time"
)

func display(msg string) {
    for i := 0; i < 3; i++ {
        time.Sleep(150 * time.Millisecond)
        fmt.Println(msg)
    }
}

func main() {
    go display("apple")
    display("orange")
}
// Output order varies due to concurrent execution

Channels

Channels are typed conduits for synchronized communication between goroutines:

ch <- val  // Send value to channel
val := <-ch  // Receive value from channel

channel := make(chan int)  // Create channel

Send and receive operations block until both sides are ready, anabling synchronization without explicit locks.

package main

import "fmt"

func calculate(values []int, resChan chan int) {
    total := 0
    for _, n := range values {
        total += n
    }
    resChan <- total
}

func main() {
    numbers := []int{5, -2, 10, -7, 3, 1}
    resultChan := make(chan int)
    
    go calculate(numbers[:3], resultChan)
    go calculate(numbers[3:], resultChan)
    
    part1, part2 := <-resultChan, <-resultChan
    fmt.Println(part1, part2, part1+part2)
}
// Output: 13 -3 10

Buffered Channels

Buffered channels accept limited values without blocking:

bufChan := make(chan string, 4)  // Channel with 4-value buffer
package main

import "fmt"

func main() {
    bufChan := make(chan int, 3)
    bufChan <- 10
    bufChan <- 20
    bufChan <- 30
    
    fmt.Println(<-bufChan)
    fmt.Println(<-bufChan)
    fmt.Println(<-bufChan)
}

Channel Closing and Range

Senders can close channels to indicate no more values:

close(ch)
val, open := <-ch  // Check if channel is open
for item := range ch { ... }  // Receive until closed
package main

import "fmt"

func sequenceGen(count int, seqChan chan int) {
    a, b := 0, 1
    for i := 0; i < count; i++ {
        seqChan <- a
        a, b = b, a+b
    }
    close(seqChan)
}

func main() {
    seqChan := make(chan int, 8)
    go sequenceGen(8, seqChan)
    
    for num := range seqChan {
        fmt.Println(num)
    }
}

Select Statement

select enables multiplexing channel operations:

select {
case msg := <-ch1:
    // Handle message
case ch2 <- data:
    // Send data
case <-quitChan:
    // Exit
}
package main

import "fmt"

func sequenceProducer(seqChan, stopChan chan int) {
    x, y := 0, 1
    for {
        select {
        case seqChan <- x:
            x, y = y, x+y
        case <-stopChan:
            fmt.Println("Terminating")
            return
        }
    }
}

func main() {
    seqChan := make(chan int)
    stopChan := make(chan int)
    
    go func() {
        for i := 0; i < 7; i++ {
            fmt.Println(<-seqChan)
        }
        stopChan <- 0
    }()
    
    sequenceProducer(seqChan, stopChan)
}

Default Selection

Default cases execute when no other channel operations are ready:

select {
case data := <-inputChan:
    // Process data
default:
    // Non-blocking fallback
}
package main

import (
    "fmt"
    "time"
)

func main() {
    tick := time.Tick(200 * time.Millisecond)
    timeout := time.After(1000 * time.Millisecond)
    
    for {
        select {
        case <-tick:
            fmt.Println("Tick")
        case <-timeout:
            fmt.Println("Timeout!")
            return
        default:
            fmt.Println("Waiting...")
            time.Sleep(100 * time.Millisecond)
        }
    }
}

Mutex Synchronization

sync.Mutex provides mutual exclusion for shared resources:

package main

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

type ProtectedCounter struct {
    counts map[string]int
    lock   sync.Mutex
}

func (pc *ProtectedCounter) Increment(key string) {
    pc.lock.Lock()
    defer pc.lock.Unlock()
    pc.counts[key]++
}

func (pc *ProtectedCounter) Value(key string) int {
    pc.lock.Lock()
    defer pc.lock.Unlock()
    return pc.counts[key]
}

func main() {
    counter := ProtectedCounter{counts: make(map[string]int)}
    for i := 0; i < 500; i++ {
        go counter.Increment("mykey")
    }
    
    time.Sleep(time.Second)
    fmt.Println(counter.Value("mykey"))
}
// Output: 500

Tags: Go goroutine channel SELECT mutex

Posted on Wed, 28 Oct 2026 16:41:23 +0000 by darklight