首页 > 代码库 > 基于共享变量的并发

基于共享变量的并发

竞争条件
竞争条件指的是程序在多个goroutine交叉执行操作时,没有给出正确的结果。竞争条件是很恶劣的一种场景,因为这种问题会一直潜伏在你的程序里,然后在非常少见的时候蹦出来,或许只是会在很大的负载时才会发生,又或许是会在使用了某一个编译器、某一种平台或者某一种架构的时候才会出现。这些使得竞争条件带来的问题非常难以复现而且难以分析诊断。
 
传统上经常用经济损失来为竞争条件做比喻,所以我们来看一个简单的银行账户程序。
示例1:一个银行账户程序
// 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
Bob的存款会在Alice存款操作中间,在余额被读到(balance + amount)之后,在余额被更新之前(balance = ...),这样会导致Bob的交易丢失。而这是因为Alice的存款操作A1实际上是两个操作的一个序列,读取然后写;可以称之为A1r和A1w。下面是交叉时产生的问题:
Data race
0
A1r      0     ... = balance + amount
B      100
A1w    200     balance = ...
A2  "= 200"
在A1r之后,balance + amount会被计算为200,所以这是A1w会写入的值,并不受其它存款操作的干预。最终的余额是$200。银行的账户上的资产比Bob实际的资产多了$100。(译注:因为丢失了Bob的存款操作,所以其实是说Bob的钱丢了).
 
数据竞争 会在两个以上的goroutine并发访问相同的变量且至少其中一个为写操作时发生。有如下3种方式避免数据竞争:

 第一种方法是不要去写变量,只去读变量。

第二种方法,避免从多个goroutine访问变量。

聊天服务器中的broadcaster goroutine是唯一一个能够访问clients map的goroutine。这些变量都被限定在了一个单独的goroutine中。

由于其它的goroutine不能够直接访问变量,它们只能使用一个channel来发送给指定的goroutine请求来查询更新变量。这也就是Go的口头禅“不要使用共享数据来通信;使用通信来共享数据”。一个提供对一个指定的变量通过cahnnel来请求的goroutine叫做这个变量的监控(monitor)goroutine。例如broadcaster goroutine会监控(monitor)clients map的全部访问。

基于共享变量的并发