Golang Concurrency

  1. Resources
  2. Channels
  3. Goroutines
  4. Select

Resources

Channels

package main

import "fmt"

// #1
// This gives the `fatal error: all goroutines are asleep - deadlock!` error, why?
// The `c <- "hello"` is a blocking call, it will wait for another goroutine to read the value
// But this never happens, because there's only the one main goroutine, so the reader `msg := <-c` never gets executed
func main() {
    c := make(chan string)
    c <- "hello"

    msg := <-c

    fmt.Println(msg)
}

// #2
// To fix, we can add a _buffer_ to the channel, telling how many messages it can receive
// Sends to a buffered channel block only when the buffer is full. Receives block when the buffer is empty.
func main() {
    // a channel which can receive 2 messages
    c := make(chan string, 2)
    c <- "hello"

    msg := <-c

    fmt.Println(msg)
}

// #3
// If we try to send or receive more messages than the buffer allows, we get
// a `fatal error: all goroutines are asleep - deadlock!` again!
func main() {
    c := make(chan string, 2)
    c <- "hello"
    c <- "world"

    // send a third message would panic in a deadlock
    // c <- "oh noes!"

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

    msg = <-c
    fmt.Println(msg)

    // receive a third message would panic in a deadlock
    // msg = <-c
    // fmt.Println(msg)
}

Goroutines

package main

import (
    "fmt"
    "time"
)

// #1
// It prints `jojo` only once, because the main goroutine continues after the `go send("jojo", c) line`
// The `msg := <- c` is a blocking call, it waits for at least one value and then it continues the execution
// Which prints and exits
func main() {
    c := make(chan string)
    go send1("jojo", c)
    msg := <-c
    fmt.Println(msg)
}

func send1(s string, c chan string) {
    for i := 0; i < 10; i++ {
        c <- s
        time.Sleep(1 * time.Second)
    }
}

// #2 what if I want to print all the values (until the for loop is finished?)
// We can make an "infinite" for loop on main to block the execution and keep receiving data
// This will work, but will finish with a `fatal error: all goroutines are asleep - deadlock!`
// Because the `send()` function is finished, but `msg` is still waiting for a value which will never be sent
// Go is smart enough to detect this at runtime, and panics with the deadlock error
// How to solve it?
func main() {
    c := make(chan string)
    go send("jojo", c)

    for {
        msg := <-c
        fmt.Println(msg)

    }
}

func send(s string, c chan string) {
    for i := 0; i < 3; i++ {
        c <- s
        time.Sleep(1 * time.Second)
    }
}

// #3 One way to solve it is to close the channel when there are no more messages to be sent
// IMPORTANT: ONLY THE SENDER SHOULD CLOSE A CHANNEL, NEVER THE RECEIVER, BECAUSE IT CAN'T KNOW IF THERE ARE MORE VALUES OR NOT!
func main() {
    c := make(chan string)
    go send("jojo", c)

    for {
        // a second return value tells us if the channel is still open or not
        // so we can take a decision, in this case, to break the loop
        msg, open := <-c
        if !open {
            break
        }

        fmt.Println(msg)
    }

    // one can also loop over a channel to achieve the same output as above, just sugar syntax
    for msg := range c {
        fmt.Println(msg)
    }
}

func send(s string, c chan string) {
    for i := 0; i < 3; i++ {
        c <- s
        time.Sleep(1 * time.Second)
    }

    // closing the channel
    close(c)
}

Select

package main

import (
    "fmt"
    "time"
)

// #1
// The output of this program is one line at time with `1s` and `100ms`, even though
// the first go routine runs way faster. Why?
// Because `fmt.Println()` is a blocking call, it will wait for the value on the second
// goroutine, which will block for one second
func main() {
    c1 := make(chan string)
    c2 := make(chan string)

    go func() {
        for {
            c1 <- "Every 100ms"
            time.Sleep(100 * time.Millisecond)
        }
    }()

    go func() {
        for {
            c2 <- "Every 1s"
            time.Sleep(1000 * time.Millisecond)
        }
    }()

    for {
        // Both are blocking calls!
        fmt.Println(<-c1)
        fmt.Println(<-c2)
    }
}

// #2 Fix
func main() {
    c1 := make(chan string, 100)
    c2 := make(chan string)

    go func() {
        for {
            c1 <- "Every 100ms"
            time.Sleep(100 * time.Millisecond)
        }
    }()

    go func() {
        for {
            c2 <- "Every 1s"
            time.Sleep(1000 * time.Millisecond)
        }
    }()

    for {
        // This fixes the problem because `select` will keep getting messages from both channels
        // in a non blocking fashion
        select {
        case msg := <-c1:
            fmt.Println(msg)
        case msg := <-c2:
            fmt.Println(msg)

            // Sends `hello` to c1
            // case c1 <- "hello":
        }
    }
}