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 的使用LockUnlock 必须成对出现,且不能嵌套使用。
  • 避免死锁:确保 goroutine 能够在合理的时间内释放锁,避免死锁。
  • Cond 的使用Cond 总是与 Mutex 一起使用,在调用 Wait 时需要持有锁。

8. 总结

MutexCond 是 Go 语言中处理并发和同步的重要工具。Mutex 用于确保共享资源的互斥访问,防止竞态条件,而 Cond 用于在条件满足前阻塞 goroutine,并协调 goroutine 之间的执行顺序。正确使用这些工具,可以编写高效且安全的并发程序。