Go Mutex
6 min
Go Mutex
1. 什么是 Mutex(互斥锁)
Mutex是 Go 语言标准库sync包提供的一个用于在多个goroutine间保护共享资源的互斥锁。它确保同一时刻只有一个
goroutine能够访问临界区(即共享资源)。
Mutex 的基本方法
Lock():获取互斥锁。如果锁已经被其他goroutine获取,则当前调用会阻塞,直到锁被释放。Unlock():释放互斥锁。如果没有goroutine持有锁,则调用会导致运行时错误。
2. 为什么需要 Mutex
- 在并发编程中,当多个
goroutine同时访问和修改共享资源时,可能会导致竞态条件(race condition),即程序的行为和结果依赖于执行的非确定性顺序。 - 使用
Mutex可以确保同一时间只有一个goroutine访问共享资源,从而避免竞态条件。
3. 如何使用 Mutex
以下是一个使用
Mutex保护共享资源的简单示例:
package main
import (
"fmt"
"sync"
)
// 定义的全局变量
var (
counter int
mu sync.Mutex // 建议在编写并发代码时,将互斥锁与它保护的资源放在一起声明。
)
func increment(wg *sync.WaitGroup) {
defer wg.Done()
mu.Lock()
counter++
mu.Unlock()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go increment(&wg)
}
wg.Wait()
fmt.Println("Final counter value:", counter)
}Cond
4. 什么是 Cond(条件变量)
Cond是 Go 语言标准库sync包提供的一个条件变量,用于阻塞goroutine直到某个条件成立。- 条件变量总是与一个互斥锁(
Mutex)一起使用,以便在检查和修改条件时确保互斥。
4.1 Cond 的基本方法
NewCond(l Locker) *Cond:创建一个条件变量,参数l是一个实现了sync.Locker接口的锁(通常是*sync.Mutex或*sync.RWMutex)。- 条件变量是同步原语,它能让一组
goroutine等待某个条件变为 true。 - 之所以需要一个锁是因为在new的时候保护共享的状态。
- 条件变量是同步原语,它能让一组
Wait(): 当前 goroutine 等待条件满足。- 调用此方法时会释放
Cond关联的互斥锁,并将当前goroutine置于等待状态。
- 调用此方法时会释放
Signal():唤醒一个等待条件的 goroutine。- 通知一个等待中的
goroutine条件已成立。如果没有goroutine在等待,则什么也不做。
- 通知一个等待中的
Broadcast():唤醒所有等待条件的 goroutine。
5. 为什么需要 Cond
条件变量主要用于协调 goroutine 之间的执行顺序。例如,一个 goroutine 在某个条件满足之前需要等待,而另一个 goroutine 会在某个时刻通知该条件已经满足。
6. 如何使用 Cond
以下是一个使用 Cond 实现简单生产者-消费者模型的示例:
package main
import (
"fmt"
"sync"
"time"
)
// Queue 表示一个并发安全的队列
type Queue struct {
items []int
cond *sync.Cond
}
// NewQueue 创建一个新的 Queue 实例
func NewQueue() *Queue {
return &Queue{
items: []int{},
cond: sync.NewCond(&sync.Mutex{}), // 使用互斥锁创建条件变量
}
}
// Enqueue 向队列添加一个元素
func (q *Queue) Enqueue(item int) {
q.cond.L.Lock() // 加锁,保护共享资源
q.items = append(q.items, item) // 将元素添加到队列
q.cond.Signal() // 通知一个等待的 goroutine
q.cond.L.Unlock() // 解锁
}
// Dequeue 从队列中移除并返回一个元素
func (q *Queue) Dequeue() int {
q.cond.L.Lock() // 加锁,保护共享资源
for len(q.items) == 0 { // 如果队列为空,等待
q.cond.Wait() // 等待条件满足
}
item := q.items[0] // 获取队列中的第一个元素
q.items = q.items[1:] // 移除第一个元素
q.cond.L.Unlock() // 解锁
return item // 返回移除的元素
}
func main() {
q := NewQueue()
// 生产者 goroutine
go func() {
for i := 0; i < 5; i++ {
q.Enqueue(i)
fmt.Printf("Produced %d\n", i)
time.Sleep(time.Second) // 模拟生产时间
}
}()
// 消费者 goroutine
go func() {
for i := 0; i < 5; i++ {
item := q.Dequeue()
fmt.Printf("Consumed %d\n", item)
}
}()
time.Sleep(6 * time.Second) // 等待所有 goroutine 完成
}7. 注意事项
- Mutex 的使用:
Lock和Unlock必须成对出现,且不能嵌套使用。 - 避免死锁:确保
goroutine能够在合理的时间内释放锁,避免死锁。 - Cond 的使用:
Cond总是与Mutex一起使用,在调用Wait时需要持有锁。
8. 总结
Mutex 和 Cond 是 Go 语言中处理并发和同步的重要工具。Mutex 用于确保共享资源的互斥访问,防止竞态条件,而 Cond 用于在条件满足前阻塞 goroutine,并协调 goroutine 之间的执行顺序。正确使用这些工具,可以编写高效且安全的并发程序。