go-并发处理

使用共享变量来实现并发

竞态

竞态的产生是因为多个对象同时访问一个对象的时候就会产生。 最常见的例子是数据库。

互斥锁:sync.Mutex

使用Lock()加锁后,不能再次对其加锁,直到Ulock()后。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var (
mu sync.Mutex
balance int
)
func Deposit(amount int) {
mu.Lock() // 获取令牌
balance = balance + amount
mu.Unlock() // 释放令牌
}
func Balance() int {
mu.Lock() // 获取令牌
b := balance
mu.Unlock() // 释放令牌
return b
}

有时候编程很难确定在所有的分支中Lock和Unlock都承兑出现了,那我们可以通过defer解决:

1
2
3
4
5
func Balance() int {
mu.Lock()
defer mu.Unlock()
return balance
}

在临界区域(Lock和Unlock之间的代码)时延迟执行的Unlock也会正确执行,这在使用recover的情况下尤其重要。当然,defer的执行成本比显示的调用Unlock略大一些,但不足以称为代码不清晰的理由。
在处理并发程序时,永远应当优先考虑清晰度,并且拒绝过早优化。在可以使用的地方,就进来使用defer来让临界区域扩展到函数结尾处。

读写互斥锁: sync.RWMutex

允许只读操作可以并发执行,但写操作需要获得完全独享的访问权限,这种锁称为多读单写锁,Go语言中的sync.RWMutex提供这种功能:

1
2
3
4
5
6
7
8
var mu sync.RWMutex
var balance int
func Balance() int {
mu.RLock() // 读锁(共享锁)
defer mu.RUnlock()
return balance
}

仅在巨大部分goroutine都在获取读锁并且锁竞争比较激烈时(即goroutine一般都需要等待后才能货到锁),RWMutex才有优势。因为RWMutex需要更复杂的内部簿记工作,所以在竞争不激烈时它比普通的互斥所慢

内存同步

延迟初始化: sync.Once

延迟一个昂贵的初始化步骤到有实际需求的时刻是一个很好的时间。预先初始化一个变量会增加程序的启动延时,并且如果时间执行时有可能根本用不上这个变量,那么初始化也不是必须的

竞态检测器

简单的把-race命令行参数加到go build、go run、go test命令里边即可使用该功能。