写点什么

Go 语言入门 16—锁

作者:良猿
  • 2022-11-14
    湖北
  • 本文字数:1706 字

    阅读完需:约 6 分钟

Go语言入门16—锁

在并发编程中,永远离不开的就是多个线程并发操作同一个资源的安全性,在 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 = 0var 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 = 0var rwLock sync.RWMutexvar 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 函数,根据最后打印的时间间隔可以发现,如果是获取到了读锁,则再次获取读锁就会很快打印出结果,如果是再次获取写锁则会等待读锁解锁之后才会获取成功。

发布于: 刚刚阅读数: 3
用户头像

良猿

关注

还未添加个人签名 2019-02-13 加入

还未添加个人简介

评论

发布
暂无评论
Go语言入门16—锁_Go_良猿_InfoQ写作社区