Man

Development & AI | Alper Akgun

Learn Go concurrency

October, 2023

Go lang's shining feature is built-in support for concurrency using goroutines and channels. Goroutines are lightweight threads well-suited for scalable and efficient applications.

You use the "go" keyword to run a function in a parallel thread; as a goroutine.

package main

import (
    "fmt"
    "time"
)

func f1(from string) {
    for i := 0; i < 3; i++ {
        fmt.Println(from, ":", i)
    }
}

func main() {

    f1("direct call")

    go f1("goroutine call")

    go func(from string) {
        fmt.Println(from)
    }("anonymous call")

    time.Sleep(time.Second)
    fmt.Println("done!")
}            

You use channels to send messages across goroutines. You can set a buffer length for channels.

package main

import (
    "fmt"
    "time"
)

func worker(done chan bool) {
    fmt.Print("working...")
    time.Sleep(time.Second)
    fmt.Println("done")

    done <- true
}

func ping(pings chan<- string, msg string) {
    pings <- msg
}

// this channel uses pings for receives, and pongs for sends.
func pong(pings <-chan string, pongs chan<- string) {
    msg := <-pings
    pongs <- msg
}

func main() {

    pings := make(chan string)

    go func() { pings <- "ping!" }()

    msg := <-messages
    fmt.Println(msg)

// create a channel which can buffer upto 2 values.
    messages := make(chan string, 2)

    messages <- "buffered"
    messages <- "channel"

    fmt.Println(<-messages)
    fmt.Println(<-messages)

// channel sync
    done := make(chan bool, 1)
    go worker(done)

// waits until the goroutine notifies as done
    <-done

// mark channels as receive-only or send-only
    pings := make(chan string, 1)
    pongs := make(chan string, 1)
    ping(pings, "passed message")
    pong(pings, pongs)
    fmt.Println(<-pongs)

// Non-blocking channel operations work with a "select" with default argument
    messages := make(chan string)
    signals := make(chan bool)

    select {
    case msg := <-messages:
        fmt.Println("received message", msg)
    default:
        fmt.Println("no message received")
    }

    msg := "hi"
    select {
    case messages <- msg:
        fmt.Println("sent message", msg)
    default:
        fmt.Println("no message sent")
    }

    select {
    case msg := <-messages:
        fmt.Println("received message", msg)
    case sig := <-signals:
        fmt.Println("received signal", sig)
    default:
        fmt.Println("no activity")
    }

// Closing a channel
    jobs := make(chan int, 5)
    finished := make(chan bool)

    go func() {
        for {
            j, more := <-jobs
            if more {
                fmt.Println("received job", j)
            } else {
                fmt.Println("received all jobs")
                finished <- true
                return
            }
        }
    }()

    for j := 1; j <= 3; j++ {
        jobs <- j
        fmt.Println("sent job", j)
    }
    close(jobs)
    fmt.Println("sent all jobs")

    <-finished

// range over a closed channel
    queue := make(chan string, 2)
    queue <- "one"
    queue <- "two"
    close(queue)

    for elem := range queue {
        fmt.Println(elem)
    }
}
            

With Golang's "select" you can wait for multiple channel operations.

package main

import (
    "fmt"
    "time"
)

func main() {

    c1 := make(chan string)
    c2 := make(chan string)
    c3 := make(chan string)

    go func() {
        time.Sleep(1 * time.Second)
        c1 <- "one"
    }()
    go func() {
        time.Sleep(2 * time.Second)
        c2 <- "two"
    }()

    go func() {
        time.Sleep(3 * time.Second)
        c3 <- "three"
    }()

    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-c1:
            fmt.Println("received: ", msg1)
        case msg2 := <-c2:
            fmt.Println("received: ", msg2)
        case msg3 := <-c3:
            fmt.Println("received: ", msg3)
        }
    }

// The following function will timeout.
    c1 := make(chan string, 1)
    go func() {
        time.Sleep(2 * time.Second)
        c1 <- "result 1"
    }()

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