# 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不想讓妳亂用