jardim do jojo

Golang Concurrency

// Publicado em: 28 de março de 2023

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":
		}
	}
}