协程安全
当程序并发执行时,如果同时操作一份数据, 就可能会出现问题,如下:
我想让钱包中的钱增加,于是开启了1万个协程,每次将钱包的钱增加1万,理想的结果最终是10000*10000 = 100000000,但运行结果却不尽人意
第一次结果:93380000,第二次结果92660000,第三次结果92560000
Go
type WalletV1 struct {
balance int
}
func (w *WalletV1) deposite(num int) {
w.balance += num
}
func (w *WalletV1) getBalance() int {
return w.balance
}
func saveMoneyV1() {
num := 10000;
walletV1 := &WalletV1{}
var wg sync.WaitGroup
wg.Add(num)
for i := 0; i < num; i++ {
go func () {
walletV1.deposite(num)
wg.Done()
}()
}
wg.Wait()
fmt.Println("余额:", walletV1.getBalance())
}
func main() {
saveMoneyV1()
}
WARNING
多个Go协程不应该同时访问那些修改共享资源的代码,这些修改共享资源的代码称为临界区
go中提供了互斥锁(Mutex)和读写锁(RWMutex)来解决这个问题
互斥锁(Mutex)
使用Mutex来改进上面的代码
Go
type WalletV2 struct {
balance int
m sync.Mutex
}
func (w *WalletV2) deposite(num int) {
w.m.Lock()
defer w.m.Unlock()
w.balance += num
}
func (w *WalletV2) getBalance() int {
return w.balance
}
func saveMoneyV2() {
num := 10000;
walletV2 := &WalletV2{}
var wg sync.WaitGroup
wg.Add(num)
for i := 0; i < num; i++ {
go func () {
walletV2.deposite(num)
wg.Done()
}()
}
wg.Wait()
fmt.Println("余额:", walletV2.getBalance())
}
func main() {
saveMoneyV2()
}
sync.Mutex实现了互斥锁,互斥锁(Mutex,mutual exclusion)提供了一种加锁机制(Locking Mechanism),能够保证在某一时刻只有一个协程在临界区运行,防止同一时间存在多个修改数据操作,它提供了Lock和UnLock两个方法来实现当一个协程被上锁后,如果其他协程来执行同样的操作时,会被阻塞,直到该协程
解锁
上面例子中,对
deposite方法加锁,保证同一时间只能有一个协程对数据进行操作
读写锁RWMutex
sync.RWMutex实现读写互斥锁,适用于读多写少的场景允许
同一时间可以有多个协程访问获取某一数据,但是同一时间只能有一个协程修改某一数据读锁与读锁兼容,读锁与写锁互斥,写锁与写锁互斥
读锁:调用RLock方法加锁,调用RUnlock解锁写锁:调用Lock方法加锁,调用Unlock解锁
示例
Go
type WalletV3 struct {
balance int
m sync.Mutex
rw sync.RWMutex
}
func (w *WalletV3) deposite(num int) {
w.rw.Lock()
defer w.rw.Unlock()
w.balance += num
}
func (w *WalletV3) getBalance() int {
w.rw.RLock()
defer w.rw.RUnlock()
return w.balance
}
func saveMoneyV3() {
num := 10000;
walletV3 := &WalletV3{}
var wg sync.WaitGroup
wg.Add(num)
for i := 0; i < num; i++ {
go func () {
walletV3.deposite(num)
wg.Done()
}()
}
wg.Wait()
fmt.Println("余额:", walletV3.getBalance())
}
func main() {
saveMoneyV3()
}