锁
在并发编程中,永远离不开的就是多个线程并发操作同一个资源的安全性,在go语言中也一样,如果有多个goroutine并发操作同一资源,就需要加锁控制并发的安全性。
代码示例:
package main
import (
"fmt"
"time"
)
var sum = 0
func main() {
go add()
go add()
time.Sleep(time.Second)
fmt.Println(sum)
}
func add() {
for i := 0; i < 10000; i++ {
sum += 1
}
}
假设有以上代码,在该代码中,定义了一个add
函数,在该函数内对全局变量sum
进行一万次加 1 ,然后在 main 函数中启动两个 goroutine 调用该函数,则理想结果下是全局变量在每一个 goroutine 中加一万次,两个 goroutine 就一共加了20000次,所以最后的 sum 的结果就应该是20000,但是在上面的代码中最后得到的 sum 结果并不是每一次都是 20000, 这就是在并发编程中出现的访问临界资源(上面代码中的临界资源就是全局变量 sum )的安全性问题。
运行结果:

如果需要保证上面的代码在多个 goroutine 访问临界资源时的安全性,就需要使用到并发编程中的锁。
在 go 语言的并发编程中,用于保证并发安全性常用的锁有互斥锁和读写锁。
互斥锁
互斥锁是一种常用的保证并发安全的锁,使用互斥锁可以保证在同一时间只能有一个 goroutine 能够访问共享资源,在 go 语言中使用 sync 包下的 Mutex 来实现互斥锁。
互斥锁定义:
var lock sync.Mutex
加锁:
lock.Lock()
解锁:
lock.Unlock()
使用互斥锁修改上面代码,使得代码能够正常运行。
package main
import (
"fmt"
"sync"
"time"
)
var sum = 0
var lock sync.Mutex // 定义互斥锁
func main() {
go add()
go add()
time.Sleep(time.Second)
fmt.Println(sum)
}
func add() {
for i := 0; i < 10000; i++ {
lock.Lock() // 加锁
sum += 1
lock.Unlock() // 解锁
}
}
上述代码使用了互斥锁,首先定义一个互斥锁 lock ,然后在 add 方法循环中进行累加之前和之后分别进行加锁和解锁,这样就可以保证在同一时刻永远只会有一个 goroutine 进行累加,其他的 goroutine 等待,当一个 goroutine 执行完并解锁之后另一个 goroutine 才会继续加锁并执行,这样就不会出现同时多个 goroutine 累加导致最后结果与预期不一致的问题。代码经过这样修改之后运行得到的 sum 就永远都是20000。
读写锁
互斥锁是完全互斥的,在实际的开发过程中,可能会出现某个 goroutine 仅仅只是读取共享资源的值,并不会对资源进行修改,这样的话使用互斥锁会降低代码的运行性能,所以这时候就需要读写锁,读写锁就是在读取资源时不会对其加锁,而是在修改资源的时候才会对其加锁。
假设有两个 goroutine ,分别是 A 和 B
-
如果 A 获取到读写锁的读锁,B 再获取读锁会直接获得,不需要等待 A 解锁。 -
如果 A 获取到读写锁的读锁,B 再获取写锁就会等待。 -
如果 A 获取到读写锁的写锁,B 再获取写锁就会等待。 -
如果 A 获取到读写锁的写锁,B 再获取读锁就会等待。
代码示例:
package main
import (
"fmt"
"sync"
"time"
)
var x = 0
var rwLock sync.RWMutex
var wg sync.WaitGroup
func main() {
for i := 0; i < 10; i++ {
wg.Add(1)
go read()
}
for i := 0; i < 10; i++ {
wg.Add(1)
go write()
}
wg.Wait()
}
// 读操作
func read() {
rwLock.RLock() // 加读锁
fmt.Println(x) // 读操作
rwLock.RUnlock() // 解锁
wg.Done()
}
func write() {
rwLock.Lock() // 加写锁
x += 1 // 写操作
time.Sleep(time.Second) // 假设写操作需要 1 秒
rwLock.Unlock() // 解锁
wg.Done()
}
在上面代码中,定义两个函数,分别是读函数 read 和写函数 write ,在 read 函数中进行读锁的加锁和解锁,在 write 函数中进行写锁的加锁和解锁,然后在 main 函数中分别启动 10 个 goroutine 执行 read 和 write 函数,根据最后打印的时间间隔可以发现,如果是获取到了读锁,则再次获取读锁就会很快打印出结果,如果是再次获取写锁则会等待读锁解锁之后才会获取成功。
原文始发于微信公众号(良猿):Java转Go—17锁
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/215791.html