互斥锁和读写互斥锁
2022年6月28日
Go 中的锁
看下面一段代码,同时竞争全局变量
x
求和。
package main
import (
"fmt"
"sync"
)
var (
x int64
wg sync.WaitGroup
)
func add() {
for i := 0; i < 5000; i++ {
x += 1
}
wg.Done()
}
func main() {
wg.Add(2)
go add()
go add()
wg.Wait()
fmt.Println(x)
}
这样下来,每次的结果多会不一样,可能你前脚把x
加到 100,后一个 goroutine 又加到了 300,最终导致结果不是我们想要的 10000
提示
即我们最终得对一个公共的资源不能同时处理数据。不然就会产生数据竞争,并发不安全。像我们上厕所一样,当有一个人在厕所,会把门关上加个锁,这样其余的人就不能进去,等你完事后再把锁打开,别人又可以进去了。
互斥锁
获取到互斥锁的任务,阻塞其他任务来获取锁
意味着同一时间只能有一个任务才能持有互斥锁
使用互斥锁来修改上面的代码:
package main
import (
"fmt"
"sync"
)
var (
x int64
wg sync.WaitGroup
lock sync.Mutex
)
func add() {
for i := 0; i < 5000; i++ {
lock.Lock() // 改之前加锁
x += 1 // 代码到了这里就变成了串行
lock.Unlock() // 改完解锁
}
wg.Done()
}
func main() {
wg.Add(2)
go add()
go add()
wg.Wait()
fmt.Println(x)
}
使用互斥锁能够保证同一时间有且只有一个 goroutine 进入临界区,其他的 goroutine 则在等待解锁;当互斥锁释放之后,等待的 goroutine 才可以获取锁进入临界区,多个 goroutine 同时等待一个锁,唤醒的策略是随机的。
package main
import (
"log"
"sync"
"time"
)
// HcMutex 是一个互斥锁
var HcMutex sync.Mutex
func runMutex(id int) {
log.Printf("[任务id:%d][尝试获取锁]", id)
HcMutex.Lock()
log.Printf("[任务id:%d][获取到了锁]", id)
time.Sleep(20 * time.Second) // 睡眠10秒钟
HcMutex.Unlock()
log.Printf("[任务id:%d][干完活了,释放锁]", id)
}
func runHcLock() {
go runMutex(1)
go runMutex(2)
go runMutex(3)
}
func main() {
runHcLock()
time.Sleep(6 * time.Minute)
/*
2021/09/02 22:15:46 [任务id:3][尝试获取锁]
2021/09/02 22:15:46 [任务id:3][获取到了锁]
2021/09/02 22:15:46 [任务id:1][尝试获取锁]
2021/09/02 22:15:46 [任务id:2][尝试获取锁]
2021/09/02 22:15:56 [任务id:1][获取到了锁]
2021/09/02 22:15:56 [任务id:3][干完活了,释放锁]
2021/09/02 22:16:06 [任务id:1][干完活了,释放锁]
2021/09/02 22:16:06 [任务id:2][获取到了锁]
2021/09/02 22:16:16 [任务id:2][干完活了,释放锁]
*/
}
读写锁
package main
import (
"log"
"sync"
"time"
)
// rwMutex 是一个读写锁
var rwMutex sync.RWMutex
func runReadLock(id int) {
log.Printf("[读任务id:%d][进入读方法,尝试获取锁]", id)
rwMutex.RLock() // 读锁
log.Printf("[读任务id:%d][获取到了读锁][开始干活,睡眠10秒]", id)
time.Sleep(10 * time.Second) // 睡眠10秒钟
rwMutex.RUnlock()
log.Printf("[读任务id:%d][完成读任务,释放读锁]", id)
}
func runWriteLock(id int) {
log.Printf("[写任务id:%d][进入写方法,尝试获取锁]", id)
rwMutex.Lock() // 写锁
log.Printf("[写任务id:%d][获取到了写锁][开始干活,睡眠10秒]", id)
time.Sleep(10 * time.Second) // 睡眠10秒钟
rwMutex.Unlock()
log.Printf("[写任务id:%d][完成写任务,释放写锁]", id)
}
// 全是写任务
func allWriteWorks() {
for i := 1; i <= 5; i++ {
go runWriteLock(i)
}
}
// 全是读任务
func allReadWorks() {
for i := 1; i <= 5; i++ {
go runReadLock(i)
}
}
// 先启动写任务
func writeFirst() {
go runWriteLock(1)
time.Sleep(1 * time.Second)
go runReadLock(1)
go runReadLock(2)
go runReadLock(3)
go runReadLock(4)
go runReadLock(5)
}
// 先启动写任务
func readFirst() {
go runReadLock(1)
go runReadLock(2)
go runReadLock(3)
go runReadLock(4)
go runReadLock(5)
time.Sleep(1 * time.Second)
go runWriteLock(1)
}
func main() {
log.Printf("执行读写锁效果的函数")
// 1. 同时多个写锁任务 => 如果并发使用读写锁的写锁时,退化成了互斥锁
//allWriteWorks()
/*
2021/09/02 22:27:28 执行读写锁效果的函数
2021/09/02 22:27:28 [写任务id:5][进入写方法,尝试获取锁]
2021/09/02 22:27:28 [写任务id:5][获取到了写锁][开始干活,睡眠10秒]
2021/09/02 22:27:28 [写任务id:1][进入写方法,尝试获取锁]
2021/09/02 22:27:28 [写任务id:2][进入写方法,尝试获取锁]
2021/09/02 22:27:28 [写任务id:3][进入写方法,尝试获取锁]
2021/09/02 22:27:28 [写任务id:4][进入写方法,尝试获取锁]
2021/09/02 22:27:38 [写任务id:1][获取到了写锁][开始干活,睡眠10秒]
2021/09/02 22:27:38 [写任务id:5][完成写任务,释放写锁]
2021/09/02 22:27:48 [写任务id:1][完成写任务,释放写锁]
2021/09/02 22:27:48 [写任务id:2][获取到了写锁][开始干活,睡眠10秒]
2021/09/02 22:27:58 [写任务id:2][完成写任务,释放写锁]
2021/09/02 22:27:58 [写任务id:3][获取到了写锁][开始干活,睡眠10秒]
2021/09/02 22:28:08 [写任务id:3][完成写任务,释放写锁]
2021/09/02 22:28:08 [写任务id:4][获取到了写锁][开始干活,睡眠10秒]
2021/09/02 22:28:18 [写任务id:4][完成写任务,释放写锁]
*/
// 2. 同时多个读锁任务,使用读锁,可以同时加多把锁
//allReadWorks()
/*
2021/09/02 22:29:16 执行读写锁效果的函数
2021/09/02 22:29:16 [读任务id:5][进入读方法,尝试获取锁]
2021/09/02 22:29:16 [读任务id:2][进入读方法,尝试获取锁]
2021/09/02 22:29:16 [读任务id:4][进入读方法,尝试获取锁]
2021/09/02 22:29:16 [读任务id:2][获取到了读锁][开始干活,睡眠10秒]
2021/09/02 22:29:16 [读任务id:4][获取到了读锁][开始干活,睡眠10秒]
2021/09/02 22:29:16 [读任务id:5][获取到了读锁][开始干活,睡眠10秒]
2021/09/02 22:29:16 [读任务id:1][进入读方法,尝试获取锁]
2021/09/02 22:29:16 [读任务id:1][获取到了读锁][开始干活,睡眠10秒]
2021/09/02 22:29:16 [读任务id:3][进入读方法,尝试获取锁]
2021/09/02 22:29:16 [读任务id:3][获取到了读锁][开始干活,睡眠10秒]
2021/09/02 22:29:26 [读任务id:3][完成读任务,释放读锁]
2021/09/02 22:29:26 [读任务id:4][完成读任务,释放读锁]
2021/09/02 22:29:26 [读任务id:2][完成读任务,释放读锁]
2021/09/02 22:29:26 [读任务id:5][完成读任务,释放读锁]
2021/09/02 22:29:26 [读任务id:1][完成读任务,释放读锁]
*/
// 3. 先启动写锁,后并发5个读锁任务
// 当有写锁存在时,读锁是施加不了的。写锁释放完,写锁可以并发的施加多个
// 写锁阻塞所有读锁
//writeFirst()
/*
2021/09/02 22:31:53 执行读写锁效果的函数
2021/09/02 22:31:53 [写任务id:1][进入写方法,尝试获取锁]
2021/09/02 22:31:53 [写任务id:1][获取到了写锁][开始干活,睡眠10秒]
2021/09/02 22:31:54 [读任务id:2][进入读方法,尝试获取锁]
2021/09/02 22:31:54 [读任务id:5][进入读方法,尝试获取锁]
2021/09/02 22:31:54 [读任务id:4][进入读方法,尝试获取锁]
2021/09/02 22:31:54 [读任务id:1][进入读方法,尝试获取锁]
2021/09/02 22:31:54 [读任务id:3][进入读方法,尝试获取锁]
2021/09/02 22:32:03 [读任务id:5][获取到了读锁][开始干活,睡眠10秒]
2021/09/02 22:32:03 [读任务id:3][获取到了读锁][开始干活,睡眠10秒]
2021/09/02 22:32:03 [写任务id:1][完成写任务,释放写锁]
2021/09/02 22:32:03 [读任务id:2][获取到了读锁][开始干活,睡眠10秒]
2021/09/02 22:32:03 [读任务id:4][获取到了读锁][开始干活,睡眠10秒]
2021/09/02 22:32:03 [读任务id:1][获取到了读锁][开始干活,睡眠10秒]
2021/09/02 22:32:13 [读任务id:1][完成读任务,释放读锁]
2021/09/02 22:32:13 [读任务id:2][完成读任务,释放读锁]
2021/09/02 22:32:13 [读任务id:4][完成读任务,释放读锁]
2021/09/02 22:32:13 [读任务id:5][完成读任务,释放读锁]
2021/09/02 22:32:13 [读任务id:3][完成读任务,释放读锁]
*/
// 4. 先启动读锁,先并发5个读锁任务,后启动一个写锁任务
// 当有读锁时,阻塞写锁
readFirst()
/*
2021/09/02 22:34:19 执行读写锁效果的函数
2021/09/02 22:34:19 [读任务id:5][进入读方法,尝试获取锁]
2021/09/02 22:34:19 [读任务id:5][获取到了读锁][开始干活,睡眠10秒]
2021/09/02 22:34:19 [读任务id:3][进入读方法,尝试获取锁]
2021/09/02 22:34:19 [读任务id:2][进入读方法,尝试获取锁]
2021/09/02 22:34:19 [读任务id:2][获取到了读锁][开始干活,睡眠10秒]
2021/09/02 22:34:19 [读任务id:3][获取到了读锁][开始干活,睡眠10秒]
2021/09/02 22:34:19 [读任务id:4][进入读方法,尝试获取锁]
2021/09/02 22:34:19 [读任务id:4][获取到了读锁][开始干活,睡眠10秒]
2021/09/02 22:34:19 [读任务id:1][进入读方法,尝试获取锁]
2021/09/02 22:34:19 [读任务id:1][获取到了读锁][开始干活,睡眠10秒]
2021/09/02 22:34:20 [写任务id:1][进入写方法,尝试获取锁]
2021/09/02 22:34:29 [读任务id:3][完成读任务,释放读锁]
2021/09/02 22:34:29 [读任务id:1][完成读任务,释放读锁]
2021/09/02 22:34:29 [读任务id:4][完成读任务,释放读锁]
2021/09/02 22:34:29 [读任务id:2][完成读任务,释放读锁]
2021/09/02 22:34:29 [读任务id:5][完成读任务,释放读锁]
2021/09/02 22:34:29 [写任务id:1][获取到了写锁][开始干活,睡眠10秒]
2021/09/02 22:34:39 [写任务id:1][完成写任务,释放写锁]
*/
time.Sleep(1 * time.Hour)
}
结论
- 同时多个写锁任务 -> 如果并发使用读写锁的写锁时,退化成了互斥锁
- 同时多个读锁任务,使用读锁,可以同时加多把锁
- 当有写锁存在时,读锁是施加不了的。写锁释放完,写锁可以并发的施加多个
- 先启动读锁,先并发 5 个读锁任务,后启动一个写锁任务,当有读锁时,阻塞写锁
Loading...