Practical Synchronization with Go
Tags: go, concurrency, distributedsystems
Published: 2020-03-26 02:19:12

## Introduction

Go is such a beautiful language when it comes to manipulating threads and concurrency programming. Even though I've only used it for a month, I cannot get over with the convenience and efficiency of goroutines and `sync` packages. This out-of-box feature makes it much easier to practice concurrency patterns(Java is so opposed to this).

This post is inspired by [MIT 6.824 Lecture 5:GO, Threads, and Raft](https://www.youtube.com/watch?v=UzzcUS2OHqo&t=40s). You can watch the lecture first and then come back for the summary notes. 

Here we only focus on the practical usage of WaitGroup/Semaphore, mutex, condition variable, and channel. 

Channel is used everwhere in Go but still we should try to avoid **buffered channel** if possible. Only the first three primitives are standard synchronization primitives and can be easily generalized to other languages.


## WaitGroup

`WaitGroup` works like the semaphore. They both have an internal counter. However, `WaitGroup` will block the thread until the counter is 0 while the semaphore block until the counter is greater than 0. The associated operations are `wg.Add()`(increase by 1), `wg.Done()`(decrease by 1), `wg.Wait()`.

A simple usage of `waitGroup` is to wait some other thread finished first.
```go
package main
import "sync"

func main() {
	var a string
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
    // Refer to variables from the enclosing scope 
		a = "hello world"
		wg.Done()
	}()
	wg.Wait()
	println(a)
}

```

`main` spawns a goroutine, and the anonymous function executes before the `println(a)` because of the `wg.Done()`. Note that the anonymous function is a closure. It can refer to the variable `a` from the enclosing scope.

Here, we can use `unbuffered channel` to do the same thing, but again, `channel` is exclusive to go, and there is always an alternative way to do the same thing with standard primitives. Those synchronization primitives are more easy to reason. 

Only use `channel` when it makes your life easier.

```go
import "fmt"
func main() {
	var a string
	var ch = make(chan int)
	go func() {
    // Refer to variables from the enclosing scope 
		a = "hello world"
		ch<-1
	}()
	<-ch
	fmt.Println(a)
}
```

### Semaphore Barrier with `WaitGroup`

A more common usage is to implement [barriers](https://vladfeinberg.com/2017/01/24/semaphore-barrier.html): To wait for a group of threads to finish first.

```go
package main
import "sync"

func main() {
	var wg sync.WaitGroup
  // Eg: Send RPC to all clients
	for i := 0; i < 5; i++ {
		wg.Add(1)
    // Have to make a copy of the i from outer scope
    // Otherwise the x will change and end up to 5. 
		go func(x int) {
			sendRPC(x)
			wg.Done()
		}(i)
	}
	wg.Wait()
}

func sendRPC(i int) {
	println(i)
}
```

In this example, `main` sends RPC requests to 5 clients and wait for their executions.

The variable `i` is passed by copying the value instead of using a closure. The `main` will block until all 5 goroutines finished. 

## Mutex

Mutex is used whenever multiple threads try to access to some share memory/data. 

The following is a bank example. Two accounts transfer money to each other in two threads. ~~A practical tip is that we can append `defer mu.Unlock()` after `mu.Lock()` so that won't mess up the order.~~( `defer mu.Unlock()` should not be used here because its in a loop instead of a enclosing function. )

```go
// bank.go
package main

import "sync"
import "time"
import "fmt"

func main() {
	alice := 10000
	bob := 10000
	var mu sync.Mutex

	total := alice + bob

	go func() {
		for i := 0; i < 1000; i++ {
			mu.Lock()
      // defer mu.Unlock()
			alice -= 1
			bob += 1
			mu.Unlock()
		}
	}()
	go func() {
		for i := 0; i < 1000; i++ {
			mu.Lock()
      // defer mu.Unlock()
			bob -= 1
			alice += 1
			mu.Unlock()
		}
	}()

	start := time.Now()
	for time.Since(start) < 1*time.Second {
		mu.Lock()
		if alice+bob != total {
			fmt.Printf("observed violation, alice = %v, bob = %v, sum = %v\n", alice, bob, alice+bob)
		}
		mu.Unlock()
	}
  fmt.Printf("alie+bob= %v\n", alice+bob)
}

```

When you have multiple shared instance, it's better to use embedded mutex as a field in a struct. Ref: [When do you embed mutex in struct in Go?](https://stackoverflow.com/questions/44949467/when-do-you-embed-mutex-in-struct-in-go)

```go
var account struct {
    sync.Mutex
    money int
}

alice := account{
    money: 10000
}
bob := account{
    money: 10000
}
```

In this example, since we need automatically mutate two variants at the same time(`alice-=1` and `bob+=1`), use a global mutex to specify a critical section is more straightforward. 

### Cancel periodic tasks with a shared variable and  mutex
We often what to do something periodically in the background, and when some service terminate, we also want to kill these background thread so that no random threads are running in the background. An easy way to do that is to use a shared variable protected by a mutex.

```go
func main() {
	time.Sleep(1 * time.Second)
	println("started")
	go periodic()
	time.Sleep(5 * time.Second) // wait for a while so we can observe what ticker does
	mu.Lock()
	done = true
	mu.Unlock()
	println("cancelled")
	time.Sleep(3 * time.Second) // observe no output
}

// Run something perioidically in background until the
// controller decide to kill them
func periodic() {
	for {
		println("tick")
		time.Sleep(1 * time.Second)
		mu.Lock()
		if done {
      mu.Unlock()
			return
		}
		mu.Unlock()
	}
}
/
```


## Condition Variable

Condition variable is used a lot in practice. 
It has three operations and is always paired with mutex(`cond:=sync.NewCond(&mu)`):
* `cond.Broadcast()`: wakes all goroutines on the cond
* `cond.Singal()`: only wake one goroutine(random choice)
* `cond.Wait()`: Automatically unlock the mutex(`c.L`) and suspend the current goroutine(go into sleep and cannot come back unless awoken by the above operations). When awoken up, `Wait()` will try to grab the lock again.

Consider the example of vote counting. One client launches a vote and sends requests to other clients. The organizer can only continue until it gets a quorum(get over half of the votes). 

Naively we can implement this by using an infinite for-loop with a mutex(busy-waiting), instead of the condition variable. However, it is not nice because the busy-waiting can burn up 100% cpu on a single core. 

```go
// Busy-waiting without condition variables
func main() {
  rand.Seed(time.Now().UnixNano())
  count := 0
  finished := 0
  var mu sync.Mutex
  for i := 0; i < 10; i++ {
    go func() {
      vote := requestVote()
      mu.Lock()
      defer mu.Unlock()
      if vote {
        count++
      }
      finished++
    }()
  }
  // Busy-waiting is always not good. Burn up 100% CPU on
  // one core. 
  for {
    mu.Lock()
    if count >= 5 || finished == 10 {
      break
    }
    mu.Unlock()
  }
  if count >= 5 {
    println("received 5+ votes!")
  } else {
    println("lost")
  }
  mu.Unlock()
}
```

The main thread keeps looping and testing until it gets a quorum. Notice it needs to both check `count`(how many of yes) and `finished`(how many voted) to fully observe the vote is finished. 

A better solution is to use a condition variable to do that. The `cond.wait()` let the current main thread go to sleep and give up the lock, so other threads have more time slot to manipulate the data. And the main thread only resumes when others wake it up.

```go
func main() {
  rand.seed(time.now().unixnano())
  count := 0
  finished := 0
  var mu sync.mutex
  cond := sync.newcond(&mu)

  for i := 0; i < 10; i++ {
    go func() {
      vote := requestvote()
      mu.lock()
      defer mu.unlock() // remember to unlock the mutex after broadcasting
      if vote {
        count++
      }
      finished++
      // broadcast to the threads that waitting on the condition variable.
      cond.broadcast()
    }()
  }
  mu.lock()   // the lock here also avoid the lost-wakeup issue.
  for count < 5 && finished != 10 {
    // this will block the current thread and give up its lock atomically 
    // so that other threads can make progress.
    cond.wait()
    // if someone wakes this up. the current thread will try to grab the
    // lock again. and then check the count and finished while holding 
    // the lock
  }
  if count >= 5 {
    println("received 5+ votes!")
  } else {
    println("lost")
  }
  mu.unlock()
}
func requestVote() bool {
  time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
  return rand.Int() % 2 == 0
}
```

There is a particular pattern we need to follow when using condition variables.  For the side that might make changes to a critical section that will change the outcome of the condition test, you always lock first then manipulate the data. Then call broadcast and call unlock afterward. 

Similarly, when you're checking the condition you grab the lock first. Then you're always checking the condition in a loop and call wait() inside it.  And unlock whenever we are done. The condition test will only execute when you grab the lock(When waked up, the end of wait() will grab the lock again).

```
mu.Lock()
// do something that might affect the condition
cond.Broadcast()
mu.Unlock()

----

mu.Lock()
while condition == false {
	cond.Wait()
}
// now condition is true, and we have the lock
mu.Unlock()

```