一、Race Condition
基本概念
并发:我们不能确定事件x和y的执行顺序,则x和y是并发的
并发(concurrent) | 并行(parallel) |
---|---|
宏观上同时,微观上交替 | 微观上同时进行 |
线程安全(thread safety):如果在并发(多线程)的情况下,这个函数依然可以正确地工作的话,那么我们就说这个函数是线程安全的,线程安全的函数不需要额外的同步工作。
竞争条件(race condition):描述一个系统或者进程的输出依赖于不受控制的事件(如并发的x和y)出现顺序或者出现时机。
数据竞争:两个不同线程中的指令访问同一块内存位置,且至少其中一条是写指令,同时不存在同步措施来保证两条指令的执行顺序。
A data race occurs when 2 instructions from different threads access the same memory location, at least one of these accesses is a write and there is no synchronization that is mandating any particular order among these accesses.
来源: stackoverflow
拓展阅读:CAS和SAS问题(原子操作相关)
比较并交换(compare and swap) wiki
ABA问题 wiki
二、避免数据竞争
1. 使用不可变(immutable)变量
即在程序初始化时便将变量的值确定下来且不再修改
优点:初始化完成后不需要同步就能实现并发安全
缺点:不可修改
2. 使用绑定(confinement)避免从多个goroutine访问变量
(1) 使用监控goroutine(monitor goroutine)
同一个goroutine内部的指令的顺序是可知的,故使用单独的goroutine来访问某一变量,其他需要访问该变量的goroutine通过channel向该goroutine发送请求来查询或更新——“不要使用共享数据来通信;使用通信来共享数据”。执行访问请求的goroutine被称为这个变量的监控(monitor)goroutine。
// 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
// Deposit() 和 Balance() 只使用channel来与teller() 通信,实际访问和修改操作是由teller() 来执行
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的条件满足,之后便跳出,故放在循环中
select {
case amount := <-deposits:
balance += amount
case balances <- balance:
}
}
}
func init() {
go teller() // start the monitor goroutine
}
(2) 使用串行绑定(serial confinement)
若变量不能在其整个生命周期内被绑定到同一个goroutine,比如该变量需要在一条pipeline上的goroutines中传递,则对pipeline中的每一个goroutine,需通过channel传递该变量的地址给下一个阶段的goroutine,并保证在传递后不再直接访问该变量,则同一时刻只有一个goroutine可以访问这个变量。
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
}
}
3. 保证goroutines访问变量时的互斥(mutual exclusion)
允许多个goroutine访问变量,但是确保在同一个时刻最多只有一个goroutine在访问。一般使用锁来实现,在《第九章 基于共享变量的并发(三)锁》中详细讨论。
1/15/2018