Go 语言入门 16—锁
锁
在并发编程中,永远离不开的就是多个线程并发操作同一个资源的安全性,在 go 语言中也一样,如果有多个 goroutine 并发操作同一资源,就需要加锁控制并发的安全性。
代码示例:
假设有以上代码,在该代码中,定义了一个add
函数,在该函数内对全局变量sum
进行一万次加 1 ,然后在 main 函数中启动两个 goroutine 调用该函数,则理想结果下是全局变量在每一个 goroutine 中加一万次,两个 goroutine 就一共加了 20000 次,所以最后的 sum 的结果就应该是 20000,但是在上面的代码中最后得到的 sum 结果并不是每一次都是 20000, 这就是在并发编程中出现的访问临界资源(上面代码中的临界资源就是全局变量 sum )的安全性问题。
运行结果:
如果需要保证上面的代码在多个 goroutine 访问临界资源时的安全性,就需要使用到并发编程中的锁。
在 go 语言的并发编程中,用于保证并发安全性常用的锁有互斥锁和读写锁。
互斥锁
互斥锁是一种常用的保证并发安全的锁,使用互斥锁可以保证在同一时间只能有一个 goroutine 能够访问共享资源,在 go 语言中使用 sync 包下的 Mutex 来实现互斥锁。
互斥锁定义:
加锁:
解锁:
使用互斥锁修改上面代码,使得代码能够正常运行。
上述代码使用了互斥锁,首先定义一个互斥锁 lock ,然后在 add 方法循环中进行累加之前和之后分别进行加锁和解锁,这样就可以保证在同一时刻永远只会有一个 goroutine 进行累加,其他的 goroutine 等待,当一个 goroutine 执行完并解锁之后另一个 goroutine 才会继续加锁并执行,这样就不会出现同时多个 goroutine 累加导致最后结果与预期不一致的问题。代码经过这样修改之后运行得到的 sum 就永远都是 20000。
读写锁
互斥锁是完全互斥的,在实际的开发过程中,可能会出现某个 goroutine 仅仅只是读取共享资源的值,并不会对资源进行修改,这样的话使用互斥锁会降低代码的运行性能,所以这时候就需要读写锁,读写锁就是在读取资源时不会对其加锁,而是在修改资源的时候才会对其加锁。
假设有两个 goroutine ,分别是 A 和 B
如果 A 获取到读写锁的读锁,B 再获取读锁会直接获得,不需要等待 A 解锁。
如果 A 获取到读写锁的读锁,B 再获取写锁就会等待。
如果 A 获取到读写锁的写锁,B 再获取写锁就会等待。
如果 A 获取到读写锁的写锁,B 再获取读锁就会等待。
代码示例:
在上面代码中,定义两个函数,分别是读函数 read 和写函数 write ,在 read 函数中进行读锁的加锁和解锁,在 write 函数中进行写锁的加锁和解锁,然后在 main 函数中分别启动 10 个 goroutine 执行 read 和 write 函数,根据最后打印的时间间隔可以发现,如果是获取到了读锁,则再次获取读锁就会很快打印出结果,如果是再次获取写锁则会等待读锁解锁之后才会获取成功。
版权声明: 本文为 InfoQ 作者【良猿】的原创文章。
原文链接:【http://xie.infoq.cn/article/a6a532d7e81119ae64c9d993f】。文章转载请联系作者。
评论