// 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 }
// Alice: go func() { bank.Deposit(200) // A1 fmt.Println("=", bank.Balance()) // A2 }() // Bob: go bank.Deposit(100) // B
Data race 0 A1r 0 ... = balance + amount B 100 A1w 200 balance = ... A2 "= 200"
服務器
架構
聊天服務器中的broadcaster goroutine是惟一一個可以訪問clients map的goroutine。這些變量都被限定在了一個單獨的goroutine中。併發
因爲其它的goroutine不可以直接訪問變量,它們只能使用一個channel來發送給指定的goroutine請求來查詢更新變量。這也就是Go的口頭禪「不要使用共享數據來通訊;使用通訊來共享數據」。一個提供對一個指定的變量經過cahnnel來請求的goroutine叫作這個變量的監控(monitor)goroutine。例如broadcaster goroutine會監控(monitor)clients map的所有訪問。dom
下面是一個重寫了的銀行的例子,這個例子中balance變量被限制在了monitor goroutine中,名爲teller:ide
// 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 }
串行綁定ui
即便當一個變量沒法在其整個生命週期內被綁定到一個獨立的goroutine,綁定依然是併發問題的一個解決方案。例如在一條流水線上的goroutine之間共享變量是很廣泛的行爲,在這二者間會經過channel來傳輸地址信息。若是流水線的每個階段都可以避免在將變量傳送到下一階段時再去訪問它,那麼對這個變量的全部訪問就是線性的。其效果是變量會被綁定到流水線的一個階段,傳送完以後被綁定到下一個,以此類推。這種規則有時被稱爲串行綁定。this
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 } }
第三種避免數據競爭的方法是容許不少goroutine去訪問變量,可是在同一個時刻最多隻有一個goroutine在訪問。這種方式被稱爲「互斥」,在下一節來討論這個主題。spa