小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
go语言提倡通过通信来共享内存,而不是通过共享内存来通信,这种话已经是老生常谈了,然而有的时候我们并不是特别需要通道,比如说,我们只是想保证每次只有一个 Go 协程能够访问一个共享的变量,从而避免冲突,这种机制叫做互斥,我们通常使用 互斥锁(Mutex) 这一数据结构来提供这种机制。
加锁和解锁
我们可以通过在代码前调用 Lock 方法,在代码后调用 Unlock 方法来保证一段代码的互斥执行。
// main 是程序的入口
func main() {
log.Println("Starting work.")
wg1.Add(2)
go incCounter(1)
go incCounter(2)
wg1.Wait()
log.Println(counter)
log.Println("Process ended.")
}
func incCounter(id int) {
defer wg1.Done()
for count := 0; count < 2; count++ {
mutex.Lock()
{
counter += id
}
mutex.Unlock()
}
}
输出为
2021/10/23 00:18:23 Starting work.
2021/10/23 00:18:23 6
2021/10/23 00:18:23 Process ended.
(当然其实不加锁也是这个结果,如果想模拟出因共享变量不一致而出现的错误,代码块应该如下)
{
value := counter
value += id
runtime.Gosched()
counter = value
}
通过这种方式构造出一个一定会造成多个协程共同读写一个变量的逻辑。
进阶
有的时候我们要考虑到程序可能会意外退出,或者说我们要允许加锁的程序意外退出而不是因为一个协程的意外退出导致锁并没有被释放,造成此后没有人可以访问共享变量。
也可以用 defer 语句来保证互斥锁一定会被解锁。