Go语言锁机制详解(sync.Mutex和sync.RWMutex)
Go 语言的锁机制是为了使多个并发之间能按照一定的顺序执行,加锁后的程序会一直占用数据和资源,直到解锁为止。
锁机制是由内置包 sync 实现的,锁类型分别为 sync.Mutex 和 sync.RWMutex,两者说明如下:
1) sync.Mutex 是互斥锁,仅支持一个 Goroutine(并发程序)对数据进行读写操作。当一个 Goroutine 获得 Mutex 锁之后,其他 Goroutine 只能等待该 Goroutine 释放锁,否则将一直处于阻塞等待状态。
2) sync.RWMutex 是读写互斥锁,它仅允许一个 Goroutine对数据执行写入操作,但支持多个 Goroutine 同时读取数据,数据读取和写入分别由不同方法实现。如果从底层原理分析,sync.RWMutex 是在 sync.Mutex 的基础上进行功能扩展,使其支持数据多读模式。
我们打开 sync.Mutex 源码看到,它以结构体方式定义,并定义了结构体方法 Lock() 和 Unlock(),源码如下所示:
1)定义全局变量 myMutex,变量类型为 sync.Mutex,它将在函数 get_data() 和主函数 main() 中使用。
2)定义函数 get_data(),由变量 myMutex 调用 Lock() 方法执行加锁处理,当函数执行完成后,再调用 Unlock() 方法执行解锁处理。
3)主函数 main() 首先执行并发处理,然后由变量 myMutex 调用 Lock() 方法执行加锁处理,由主函数 main() 占用资源执行遍历输出,最后调用 Unlock() 方法执行解锁处理,将资源释放,由并发程序的函数 get_data() 占用。
如果程序执行多个并发操作,由于每个并发的执行时间各不相同,sync.Mutex 只能保证当前只有一个并发占用资源,但不能改变并发的执行顺序,比如在上述代码的主函数 main() 中执行函数 get_data() 的多次并发。
主函数 main() 修改如下:
我们再看读写互斥锁 sync.RWMutex,打开 sync.RWMutex 源码看到,如下所示:
sync.RWMutex 提供了 4 个常用的结构体方法,分别为 RLock()、RUnlock()、Lock() 和 Unlock()。其中 RLock() 和 RUnlock() 支持数据多读模式,Lock() 和 Unlock() 支持数据单写模式,使用示例如下:
sync.RWMutex 的读写操作不能只从字面上理解为数据的读取和写入,使用结构体方法 RLock() 和 RUnlock() 也能实现数据写入,但执行结果会出现误差,比如将上述代码的函数 write() 改用 RLock() 和 RUnlock(),程序运行结果为:
在使用 RLock() 和 RUnlock()、Lock() 和 Unlock() 的时候,它们必须成对出现。如果使用 RLock() 加锁,Unlock() 解锁,程序会提示死锁异常(fatal error: all goroutines are asleep- deadlock!)。
锁机制是由内置包 sync 实现的,锁类型分别为 sync.Mutex 和 sync.RWMutex,两者说明如下:
1) sync.Mutex 是互斥锁,仅支持一个 Goroutine(并发程序)对数据进行读写操作。当一个 Goroutine 获得 Mutex 锁之后,其他 Goroutine 只能等待该 Goroutine 释放锁,否则将一直处于阻塞等待状态。
2) sync.RWMutex 是读写互斥锁,它仅允许一个 Goroutine对数据执行写入操作,但支持多个 Goroutine 同时读取数据,数据读取和写入分别由不同方法实现。如果从底层原理分析,sync.RWMutex 是在 sync.Mutex 的基础上进行功能扩展,使其支持数据多读模式。
我们打开 sync.Mutex 源码看到,它以结构体方式定义,并定义了结构体方法 Lock() 和 Unlock(),源码如下所示:
type Mutex struct{
state int32
sema uint32
}
type Locker interface{
Lock()
Unlock()
}
实际应用中只需定义结构体 Mutex,分别调用结构体方法 Lock() 和 Unlock() 即可实现加锁处理,应用示例如下:
package main
import (
"fmt"
"sync"
"time"
)
// 定义互斥锁Mutex的全局变量
var (
myMutex sync.Mutex
)
func get_data(name string) {
// 加锁处理
myMutex.Lock()
// 程序执行
fmt.Printf("这是:%v\n", name)
// 解锁处理
myMutex.Unlock()
}
func main() {
// 执行并发
go get_data("get_data")
// 加锁处理
myMutex.Lock()
// 程序执行
fmt.Printf("这是:%v\n", "Main")
for i := 0; i < 3; i++ {
// 每一秒输出一行数据
time.Sleep(1 * time.Second)
fmt.Printf("等待时间:%v秒\n", i+1)
}
// 解锁处理
myMutex.Unlock()
// 等待延时,为了等待并发程序执行完成
// 可以改为WaitGroup等待
time.Sleep(2 * time.Second)
}
运行上述代码,运行结果为:
这是:Main
等待时间:1秒
等待时间:2秒
等待时间:3秒
这是:get_data
1)定义全局变量 myMutex,变量类型为 sync.Mutex,它将在函数 get_data() 和主函数 main() 中使用。
2)定义函数 get_data(),由变量 myMutex 调用 Lock() 方法执行加锁处理,当函数执行完成后,再调用 Unlock() 方法执行解锁处理。
3)主函数 main() 首先执行并发处理,然后由变量 myMutex 调用 Lock() 方法执行加锁处理,由主函数 main() 占用资源执行遍历输出,最后调用 Unlock() 方法执行解锁处理,将资源释放,由并发程序的函数 get_data() 占用。
如果程序执行多个并发操作,由于每个并发的执行时间各不相同,sync.Mutex 只能保证当前只有一个并发占用资源,但不能改变并发的执行顺序,比如在上述代码的主函数 main() 中执行函数 get_data() 的多次并发。
主函数 main() 修改如下:
func main() {
// 执行并发
go get_data("AAA")
go get_data("BBB")
time.Sleep(2 * time.Second)
}
运行主函数 main(),字符串 AAA 和 BBB 的输出顺序各不相同,这也说明 sync.Mutex 无法保证并发程序的执行顺序。我们再看读写互斥锁 sync.RWMutex,打开 sync.RWMutex 源码看到,如下所示:
type RWMutex struct{
w Mutex
writerSem uint32
readerSem uint32
readerCount int32
readerWait int32
}
它的结构体成员 w 是结构体 Mutex,这说明 sync.RWMutex 是在 sync.Mutex 的基础上进行扩展的。sync.RWMutex 提供了 4 个常用的结构体方法,分别为 RLock()、RUnlock()、Lock() 和 Unlock()。其中 RLock() 和 RUnlock() 支持数据多读模式,Lock() 和 Unlock() 支持数据单写模式,使用示例如下:
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
// 全局变量
var count int
// 定义读写锁
var rLock sync.RWMutex
// 定义同步等待组
var wg sync.WaitGroup
// 数据读取函数
func read(i int) {
// 加锁
rLock.RLock()
// 设置延时
t := time.Duration(i * 2) * time.Second
time.Sleep(t)
fmt.Printf("读操作,等待时间:%v 数据=%d\n", t.Seconds(), count)
// 解锁
rLock.RUnlock()
wg.Done()
}
// 数据写入函数
func write(i int) {
// 加锁
rLock.Lock()
// 写入数据
count = rand.Intn(1000)
// 设置延时
t := time.Duration(i * 2) * time.Second
time.Sleep(t)
fmt.Printf("写操作,等待时间:%v 数据=%d\n", t.Seconds(), count)
// 解锁
rLock.Unlock()
wg.Done()
}
func main() {
// 设置同步等待组
wg.Add(6)
// 设置随机数种子,保证每次随机数不相同
rand.Seed(time.Now().UnixNano())
// 执行6次并发
for i := 1; i < 4; i++ {
go write(i)
}
for i := 1; i < 4; i++ {
go read(i)
}
// 等待同步等待组执行并发
wg.Wait()
}
运行上述代码,运行结果为:
写操作,等待时间:2 数据=719
读操作,等待时间:2 数据=719
读操作,等待时间:4 数据=719
读操作,等待时间:6 数据=719
写操作,等待时间:4 数据=395
写操作,等待时间:6 数据=417
- 当程序执行读操作的时候,所有写操作处于阻塞状态。
- 当程序执行读操作的时候,其他读操作能同时执行。
- 当程序执行写操作的时候,所有操作都处于阻塞状态。
sync.RWMutex 的读写操作不能只从字面上理解为数据的读取和写入,使用结构体方法 RLock() 和 RUnlock() 也能实现数据写入,但执行结果会出现误差,比如将上述代码的函数 write() 改用 RLock() 和 RUnlock(),程序运行结果为:
读操作,等待时间:2 数据=170
写操作,等待时间:2 数据=170
写操作,等待时间:4 数据=170
读操作,等待时间:4 数据=170
写操作,等待时间:6 数据=170
读操作,等待时间:6 数据=170
在使用 RLock() 和 RUnlock()、Lock() 和 Unlock() 的时候,它们必须成对出现。如果使用 RLock() 加锁,Unlock() 解锁,程序会提示死锁异常(fatal error: all goroutines are asleep- deadlock!)。
ICP备案:
公安联网备案: