---
# System prepended metadata

title: Chapter 9. Concurrency with Shared Variables

---

# Chapter 9. Concurrency with Shared Variables

## Reference

https://notes.shichao.io/gopl/ch9/
https://github.com/sean1093/golang-study-group/blob/master/Notes/Chapter%209%20%E5%85%B1%E7%94%A8%E8%AE%8A%E6%95%B8%E4%B8%A6%E8%A1%8C%E6%80%A7.md

## 一句話總結此單元

讓程式高效率且正確地執行


## 前情提要

### sequential program
只有一個 goroutine 的程式，會按照程式碼邏輯的順序執行

### concurrent（交錯
在有兩個以上 goroutine 的程式裡 
x, y 兩個 event 分別在不同的 goroutine 中
當我們沒辦法確定 x, y 誰會先執行的時候
就可以說 x, y 兩個 event 是 concurrent

### concurrency-safe
交錯執行的時候不會影響結果

#### concurrency-safe function
當一個 function 陸陸續續被不同的 goroutine 呼叫
還不會造成執行錯誤 就稱它是一個 concurrency-safe function
例：

```go=
package main

import (
	"fmt"
	"math"
	"time"
)

func concurrentSafe(f float64) {
	for i := 0; i < 5; i++ {
		time.Sleep(100 * time.Millisecond)
		fmt.Println(f)
	}
}

func main() {
	go concurrentSafe(math.Abs(-42))
	concurrentSafe(math.Abs(-4))
}

}
```
在輸出結果可以看到 `concurrentSafe(math.Abs(-42))`跑在另一個 goroutine 上跟 `concurrentSafe(math.Abs(-4))`交錯執行
但依然是正常輸出結果，這是因為 `math.Abs(-4)` 是 `exported package-level functions`
```
>> go run main.go
42
4
4
42
42
4
4
42
42
4
```
#### concurrency-safe type
所有 accessible methods 都是 concurrency-safe
#### concurrency-safe program 
禁止 concurrent access 所有變數，除了 concurrency-safe type。
具體執行方式有兩個
1. 限制變數只在同一個 goroutine 被 access
2. 維護一個每次都只允許單一存取的不會被執行順序亂衝康的值
    原文：Maintaining a higher-level invariant of mutual exclusion

## Race Conditions
程式在多個 goroutine 交錯執行後，輸出錯誤結果
通常發生在資料量很大的時候，所以很難重新模擬出同樣的錯誤情境來偵錯

### data race
data race 是一種 Race Condition
發生在多個 goroutine 對同一個變數交錯存取時並且至少有一個存取是寫入的時候
#### primitive data type: int
```go=
// Package bank implements a bank with only one account.
package bank

var balance int

func Deposit(amount int) { balance = balance + amount }

func Balance() int { return balance }
```

```go=
// Alice:
go func() {
    bank.Deposit(200) // A1
    fmt.Println("=", bank.Balance()) // A2
}()

// Bob:
go bank.Deposit(100) // B
```
現在有兩個 goroutine
三個 event: A1, A2, B
執行順序導致不同的結果
```
A1 -> A2 -> B  >> =200
B  -> A1 -> A2 >> =300
A1 -> B  -> A2 >> =300 
```
#### composite data type: slice
```go=
var x []int 
go func() { x = make([]int, 10) }()
go func() { x = make([]int, 1000000) }()
x[999999] = 1 // NOTE: undefined behavior; memory corruption possible
```
slice 由 pointer, length, capacity 組成
所以在交錯執行的時候有可能發生以下情形
pointer 指到第二行make出的length=10的記憶體位置
length被第三行定義成1000000
導致第四行的`x[999999]`其實指向沒被定義的記憶體位置
### Avoiding a data race
有三種方式可以預防 data race
#### Avoid writing the variable
這個程式會在第一次 call 到`Icon(name)`才會去將指定 `name` 的 `icon` 寫進 `icons`(map)
當同時有多個 goroutine 在 call 時，`icons`(map)就會發生 data race
```go=
var icons = make(map[string]image.Image)

func loadIcon(name string) image.Image

// NOTE: not concurrency-safe!
func Icon(name string) image.Image {
    icon, ok := icons[name]
    if !ok {
    icon = loadIcon(name)
    icons[name] = icon
    }
    return icon
}
```
所以如果先把 `icons`(map) 定義好 每次 call `Icon(name)`時就只是讀取的動作
預防了 data race 
```go=
var icons = map[string]image.Image{
    "spades.png": loadIcon("spades.png"),
    "hearts.png": loadIcon("hearts.png"),
    "diamonds.png": loadIcon("diamonds.png"),
    "clubs.png": loadIcon("clubs.png"),
}

// Concurrency-safe.
func Icon(name string) image.Image { return icons[name] }
```
#### Avoid accessing the variable from multiple goroutines
> Go mantra:
> "Do not communicate by sharing memory; instead, share memory by communicating."
在這邊我們把 `balances` 綁定在 `teller` 這個 monitor goroutine
代表我們只能透過 `teller` 裡面的 channel 來去存取 `balances`
```go=
// Package bank provides a concurrency-safe bank with one account.
package bank

var deposits = make(chan int) // send amount to deposit
var balances = make(chan int) // receive balance

func Deposit(amount int) { deposits <- amount }
func Balance() int       { return <-balances }

func teller() {
    var balance int // balance is confined to teller goroutine
    for {
        select {
        case amount := <-deposits:
            balance += amount
        case balances <- balance:
        }
    }
}

func init() {
    go teller() // start the monitor goroutine
}
```
在這個例子 Cake 則是先被綁定在 baker 然後在被綁定在 icer
```go=
type Cake struct{ state string }

func baker(cooked chan<- *Cake) {
    for {
        cake := new(Cake)
        cake.state = "cooked"
        cooked <- cake // baker never touches this cake again
    }
}

func icer(iced chan<- *Cake, cooked <-chan *Cake) {
    for cake := range cooked {
        cake.state = "iced"
        iced <- cake // icer never touches this cake again
    }
}

```
#### Allow only one goroutine to access the variable at a time
就是我們接下來要講的 Mutual exclusion





## Mutual Exclusion: sync.Mutex

不太懂`struct{}{}`是啥 但這是 binary semaphone

```go=
var (
    sema    = make(chan struct{}, 1) // a binary semaphore guarding balance
    balance int
)

func Deposit(amount int) {
    sema <- struct{}{} // acquire token
    balance = balance + amount
    <-sema // release token
}

func Balance() int {
    sema <- struct{}{} // acquire token
    b := balance
    <-sema // release token
    return b
}
```
sync.Mutex 藉由下面的方式來實作 Mutual Exclusion
* `Lock()`來得到`lock`這個token
* `UnLock()`釋放`lock`
* 有`lock`才能進入 critical section
```go =
import "sync"

var (
    mu      sync.Mutex // guards balance
    balance int
)

func Deposit(amount int) {
    mu.Lock()
    balance = balance + amount // critical section
    mu.Unlock()
}

func Balance() int {
    mu.Lock()
    b := balance // critical section
    mu.Unlock()
    return b
}
```
這邊用`defer`就能確保程式就算遇到panic也會執行`Unlock()`
```go=
func Balance() int {
    mu.Lock()
    defer mu.Unlock()
    return balance // critical section
}

```

## Read/Write Mutexes: sync.RWMutex
* 適合讀取的次數遠大於寫入的次數的情境
* 在寫入的時候會禁止讀寫，但讀的時候不會限制其他人讀
* 相較`sync.Mutex`來說比較耗資源
```go=
var mu sync.RWMutex
var balance int

func Balance() int {
    mu.RLock() // readers lock
    defer mu.RUnlock()
    return balance
}

```

## Memory Synchronization
[ref](https://github.com/sean1093/golang-study-group/blob/master/Notes/Chapter%209%20%E5%85%B1%E7%94%A8%E8%AE%8A%E6%95%B8%E4%B8%A6%E8%A1%8C%E6%80%A7.md#94-%E8%A8%98%E6%86%B6%E9%AB%94%E5%90%8C%E6%AD%A5)
> 多處理器的架構下, 各個處理器有自己的cache, cache不一定會更新到最新狀態, 不同的編輯器或CPU會有不同的行為
> 用mutex或是channel可以確保資料被更新到各個cache裡

## Lazy Initialization: sync.Once
`sync.Once`裡面含 mutex 跟一個 bool
* mutex: 讓初始化的過程只有一個 go routine 在做
* bool: 確認初始化只做過一次
```go=

var loadIconsOnce sync.Once
var icons map[string]image.Image

func loadIcons() {
    icons = make(map[string]image.Image)
    icons["spades.png"] = loadIcon("spades.png")
    icons["hearts.png"] = loadIcon("hearts.png")
    icons["diamonds.png"] = loadIcon("diamonds.png")
    icons["clubs.png"] = loadIcon("clubs.png")
}

// Concurrency-safe.
func Icon(name string) image.Image {
    loadIconsOnce.Do(loadIcons)
    return icons[name]
}
```

## The Race Detector

go 提供動態分析的工具，來偵測 data race 的情形
用法:可以在 `go run` `go test`後面加上`-race`的 flag

```
go run -race main.go
```
## Example: Concurrent Non-Blocking Cache(真看不懂來日方長)
## Goroutines and Threads
### OS thread v.s. Goroutine
| Name               | OS thread           | Goroutine              |
| ------------------ | ------------------- | ---------------------- |
| stack size         | fixed, usually 2MB  | scalable, 2KB ~ 1GB    |
| schedule by        | kernel              | Go scheduler           |
| invoke schedule by | hardware timer      | sleep, block, or mutex |
| how to schedule    | full context switch | put it to sleep        |

### Goroutine: m:n scheduling
m Goroutine on n OS threads

### Goroutine: GOMAXPROCS
how many OS threads may be executing Go code simultaneously. (# CPUs)
Example:
single OS thread:
$ GOMAXPROCS=1 go run hacker-cliché.go
111111111111111111110000000000000000000011111...
two OS threads:(幾乎是同時交互進行)
$ GOMAXPROCS=2 go run hacker-cliché.go
010101010101010101011001100101011010010100110...

### Goroutines have no Identity
故意的XD不想讓妳亂用
