Golang基础-mutex的正常模式和饥饿模式2

在并发编程中,Mutex(互斥锁)用于保护共享资源的访问,确保同一时刻只有一个 goroutine 可以访问共享资源。Mutex 在并发编程中有两种常见的使用模式:正常模式(Normal Mode)和 饥饿模式(Starvation Mode)。

正常模式(Normal Mode)

正常模式是最常见的并发锁定行为,即当一个 goroutine 获取到锁时,其他 goroutine 必须等待,直到当前持有锁的 goroutine 释放锁,才能继续执行。

工作原理:
  1. 初始状态Mutex 是未锁定的(Unlocked),任何 goroutine 都可以成功调用 Lock() 获取锁。

  2. 锁定状态:当一个 goroutine 获取到锁(调用 Lock()),它就进入了临界区,其他 goroutine 尝试获取锁时会被阻塞,直到锁被释放。

  3. 解锁状态:当持有锁的 goroutine 执行完临界区代码并调用 Unlock() 时,锁被释放。此时,等待的 goroutine 中一个会被唤醒,获取锁并进入临界区。

  4. 无优先级:锁的获取顺序由操作系统的调度器决定,通常是按照 goroutine 启动的顺序来调度。

影响和特点:
  • 公平性:正常模式下,Mutex 的调度是公平的,不存在一个 goroutine 总是能占有锁,其他 goroutine 需要等待。

  • 效率:通常情况下,正常模式是并发控制的标准模式,不会造成阻塞时间过长。

正常模式的例子
package main
​
import (
    "fmt"
    "sync"
)
​
var mu sync.Mutex
​
func criticalSection(id int) {
    mu.Lock()   // 获取锁
    defer mu.Unlock() // 解锁
    fmt.Printf("Goroutine %d in critical section\n", id)
}
​
func main() {
    var wg sync.WaitGroup
    for i := 1; i <= 3; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            criticalSection(id)
        }(i)
    }
    wg.Wait()
}

结果

  • 只要一个 goroutine 获取了锁,其他 goroutine 会被阻塞,直到锁被释放,通常不会出现饥饿的情况。

  • goroutine 执行的顺序可能是 1 -> 2 -> 3,或者其他顺序。


饥饿模式(Starvation Mode)

饥饿模式是指某些 goroutine 因为其他 goroutine 一直获取锁而被无限期地阻塞。这种情况通常发生在锁的获取没有公平性时,导致某些 goroutine 始终无法获得锁,进而无法执行。

触发时机

饥饿模式通常在以下情况下发生:

  1. 无限循环的锁竞争:如果某些 goroutine 获取锁的时间远长于其他 goroutine 的等待时间,后者就有可能长时间甚至永久地被饿死。

  2. 不公平调度:如果 goroutine 在获取锁时没有公平的调度机制,某些优先级较高的 goroutine 会一直占用锁,其他 goroutine 就无法获得锁。

  3. 资源访问不均:如果资源访问的模式不均,可能会出现某些 goroutine 无法获得访问资源的机会,形成饥饿状态。

饥饿模式的影响:
  • 不公平性:某些 goroutine 可能永远无法获得锁,导致长时间无法执行,造成“饥饿”。

  • 系统效率低:当某些 goroutine 被长期阻塞时,整个系统的效率会受到影响,某些任务始终得不到执行。

饥饿模式的例子:
package main
​
import (
    "fmt"
    "sync"
    "time"
)
​
var mu sync.Mutex
​
func criticalSection(id int) {
    mu.Lock()   // 获取锁
    defer mu.Unlock() // 解锁
    fmt.Printf("Goroutine %d in critical section\n", id)
    time.Sleep(1 * time.Second) // 模拟工作
}
​
func main() {
    var wg sync.WaitGroup
​
    // 启动多个 goroutine
    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            criticalSection(id)
        }(i)
    }
​
    wg.Wait()
}

可能的结果

  • 假设程序中有多个 goroutine 试图获取锁,但是其中某些 goroutine 执行的时间比其他 goroutine 长,导致它们在 Unlock 后反复获取锁,而其他 goroutine 一直无法获得锁。

  • 这种情况下,长时间运行的 goroutine 会导致其他 goroutine 被“饿死”。


避免饥饿模式的策略

为了避免饥饿模式,我们可以考虑以下几种策略:

  1. 公平锁(Fair Lock)

    • 可以通过引入公平锁机制来避免饥饿。在公平锁中,锁的分配是按请求的顺序进行的,确保每个请求锁的 goroutine 都有机会获取锁。

    • Go 标准库的 sync.Mutex 并没有内建的公平性,因此开发者需要手动实现公平锁,或者使用 sync/atomic 和额外的数据结构来确保公平性。

  2. 资源分配优先级

    • 在设计系统时,合理安排 goroutine 的优先级,避免某些 goroutine 因为优先级低而长期被阻塞。

    • 可以使用 context.Context 或者通过调度优先级控制,确保每个任务能够得到公平的资源访问机会。

  3. 重试机制

    • 对于一些可能陷入饥饿状态的 goroutine,可以使用重试机制,让它们在尝试一定次数后再次进行锁的请求。

  4. 适当的锁粒度

    • 降低锁的粒度,减少锁的持有时间,从而减少并发竞争的强度。这种方式有时可以有效地避免因锁过长时间持有而导致的饥饿问题。


总结

  • 正常模式:在正常的锁定模式下,Mutex 的行为是公平的,Mutex 释放后,等待的 goroutine 会按顺序依次获取锁。此模式下不会出现任何饥饿问题。

  • 饥饿模式:饥饿模式发生在调度不公平时,某些 goroutine 被无限期地阻塞,无法获得锁,导致无法执行,最终造成性能问题。

  • 如何避免饥饿模式:通过引入公平锁、合理设置优先级、使用重试机制、降低锁粒度等方式,可以避免饥饿模式的发生。

在实际编程中,通常使用 Mutex 来保证同步,然而对于一些复杂的并发场景,开发者需要特别注意如何平衡锁的公平性和效率,避免发生饥饿问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yy_Yyyyy_zz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值