// 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