Go

Golang的特性三(互斥锁)----sync.Mutex包的用法

Royal
2023-07-23 / 0 评论 / 17 阅读 / 正在检测是否收录...

1. sync.Mutex 的实现原理

  • 概念
    sync.Mutex 是 Go 语言标准库中用于实现互斥锁的类型,用于保护共享资源,确保在同一时刻只有一个 goroutine 可以访问被保护的资源。
    sync.Mutex是一个结构体对象,适用于读写不确定的场景(即读写次数没有明显区别,且只允许有一个读或写的场景),该锁也称为全局锁。
    m7g6zvq6.png
  • 内部状态
     - sync.Mutex 内部有一个 state 字段,用于表示锁的状态。这个状态使用位操作来表示不同的信息,包括锁是否被持有、是否有等待的 goroutine 等。
     - 例如,低 1 位可能表示锁是否被持有,其他位可能表示等待队列的信息。
  • 锁定操作(Lock 函数)
     - 当调用 Lock 函数时,会尝试将 state 置为锁定状态。如果锁已经被其他 goroutine 持有,当前 goroutine 会进入等待状态,可能会被阻塞。
     - 这通常涉及到自旋等待和排队等待两种机制。
     - 自旋等待:在某些情况下,sync.Mutex 会先进行一定次数的自旋等待,尝试快速获取锁,避免立即进入阻塞状态,以提高性能。
     - 排队等待:如果自旋等待失败,会将当前 goroutine 加入到等待队列中,等待唤醒。
  • 解锁操作(Unlock 函数):
     - 当调用 Unlock 函数时,会将 state 置为未锁定状态,并唤醒等待队列中的一个 goroutine,让它可以获取锁。

2. 代码示例

package main
 
import (
    "fmt"
    "sync"
    "time"
)
 
var (
    mu    sync.Mutex
    count int
)
 
func increment() {
    mu.Lock()
    count++
    mu.Unlock()
}
 
func main() {
    var wg sync.WaitGroup
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            increment()
        }()
    }
    wg.Wait()
    fmt.Println(count)
}

m7g7gc26.png
代码解释:

  • 变量声明:
     - mu 是 sync.Mutex 类型的互斥锁,用于保护 count 变量。
     - count 是一个共享变量,多个 goroutine 会对其进行操作。
  • increment 函数:
     - 在 increment 函数中,首先调用 mu.Lock() 来获取锁,确保在操作 count 时不会有其他 goroutine 同时操作。
     - 然后对 count 进行加 1 操作。
     - 最后调用 mu.Unlock() 释放锁,让其他 goroutine 可以获取锁并操作 count。
  • main 函数:
     - 使用 sync.WaitGroup 来等待所有的 goroutine 完成。
     - 启动 1000 个 goroutine,每个 goroutine 调用 increment 函数
    总结
  • 通过以上代码运行我们发现当加了互斥锁之后,代码每次都会输出1000,这是因为无论我们会开多少个协程count值都会依次加1直到循环完成,而且每个groutine必须等锁释放之后才能操作count值,所以值就是循环的次数。
  • 当我们把互斥锁代码注释掉后,我们会发现输出的结果值不固定而且都小于1000,这是因为多个 goroutine 同时并发地访问和修改 count,导致了数据竞争(Race Condition),在并发环境下,如果没有锁的保护,多个 goroutine 可能会同时读取 count 的旧值并进行加 1 操作,导致部分加 1 操作被覆盖,最终结果小于预期值。

3. 使用 sync.Mutex 的注意事项

正确的锁操作顺序

  • 确保在修改共享资源之前获取锁,在修改完成后释放锁。
  • 避免在持有锁的情况下进行长时间的操作或可能阻塞的操作,如 I/O 操作,这可能导致其他 goroutine 长时间等待。

避免死锁:

  • 确保锁的获取和释放顺序是一致的,避免出现循环等待的情况。
  • 避免在已经持有锁的情况下再次获取锁,除非使用 sync.RWMutex 的 RLock 等功能。

性能考虑:

  • 对于频繁访问的共享资源,使用 sync.Mutex 可以保证数据一致性,但会带来一定的性能开销。
    以下是一个可能导致死锁的错误示例:

    func wrongLock() {
        mu.Lock()
        defer mu.Lock() 
        // 错误:这里应该是 mu.Unlock()
        count++
    }

    在这个错误示例中,defer 语句错误地再次调用了 Lock 而不是 Unlock,导致死锁。

0

评论

博主关闭了当前页面的评论