Skip to content
On this page

协程安全

当程序并发执行时,如果同时操作一份数据, 就可能会出现问题,如下:

我想让钱包中的钱增加,于是开启了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),能够保证在某一时刻只有一个协程在临界区运行,防止同一时间存在多个修改数据操作,它提供了LockUnLock两个方法来实现

  • 当一个协程被上锁后,如果其他协程来执行同样的操作时,会被阻塞,直到该协程解锁

上面例子中,对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()
}