协程安全
当程序并发执行时,如果同时操作一份数据, 就可能会出现问题,如下:
我想让钱包中的钱增加,于是开启了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()
}