go的Mutex实现原理及演进

news2024/11/13 14:40:10

下面的这个是对于昨天晚上读的几篇关于go中锁的文章知识点的总结

文章目录

  • 1. 引言
        • 1.1 并发编程的挑战
        • 1.2 Mutex 的角色
        • 1.3 Mutex 设计的演进
        • 1.4 技术追求的美妙
      • 引言部分详细解释
      • 引言部分注意点
      • 引言部分流程图
  • 2. Mutex 架构演进
        • 2.1 初版 Mutex 设计
        • 2.2 性能优化 - 给新人机会
        • 2.3 进一步优化 - 多给些机会
        • 2.4 公平性保障 - 解决饥饿
        • 2.1 初版 Mutex 详解
        • 2.2 给新人机会详解
        • 2.3 多给些机会详解
        • 2.4 解决饥饿详解
        • 2.5 流程图分析
        • 2.6 注意点
  • 3. Mutex 核心实现分析
        • 3.1 CAS 操作详解
        • 3.2 信号量(Semaphore)详解
        • 3.3 位操作详解
        • 3.4 Mutex 状态字段(state)的位字段分析
        • 3.5 流程图分析 - CAS 操作流程
        • 3.6 注意点
  • 4. Mutex 的性能与公平性权衡
        • 4.1 性能优化
        • 4.2 公平性保障
        • 4.3 性能与公平性的平衡
        • 4.4 性能优化详解
        • 4.5 公平性保障详解
        • 4.6 流程图分析 - 性能与公平性权衡
        • 4.7 注意点
  • 5. Mutex 的设计哲学
        • 5.1 稳定性与兼容性
        • 5.2 扩展性与前瞻性
        • 5.3 简洁性与复杂性的平衡
        • 5.4 性能与公平性的权衡
        • 5.5 设计哲学的实现
        • 5.6 流程图分析 - 设计哲学的体现
        • 5.7 注意点
  • 6. 思考题深入解析
        • 6.1 Mutex 的 state 字段
        • 6.2 goroutine 等待数量限制
        • 6.3 流程图分析 - state 字段的位操作
        • 6.4 注意点
        • 6.5 思考题答案
  • 7. 结论与展望
        • 7.1 结论
        • 7.2 展望
        • 7.3 流程图分析 - Mutex 的演进
        • 7.4 注意点
        • 7.5 个人视角
  • 8. 附录
        • 8.1 Mutex 相关代码片段及注释
          • 8.1.1 初版 Mutex 实现
          • 8.1.2 给新人机会的 Mutex 实现
          • 8.1.3 解决饥饿的 Mutex 实现
        • 8.2 位操作示例及解释
          • 8.2.1 位操作基础
          • 8.2.2 检查和修改状态
        • 8.3 个人学习总结

1. 引言

1.1 并发编程的挑战

并发编程是软件设计中的一个复杂领域,它涉及到多个执行线程或goroutine同时进行操作,这可能导致数据竞争、死锁和其他同步问题。Go语言通过提供并发原语,如goroutine和channel,简化了并发编程的复杂性。

1.2 Mutex 的角色

Mutex(互斥锁)是并发编程中用于保护共享资源不被多个goroutine同时修改的一种同步机制。在Go中,sync.Mutex提供了基本的互斥功能,确保在任意时刻只有一个goroutine可以访问临界区。

1.3 Mutex 设计的演进

Go语言的Mutex实现随着语言的发展而演进。最初,它的设计非常简单,但随着对性能和公平性要求的提高,Mutex的实现变得越来越复杂。这种演进体现了Go语言设计者对于实现高性能并发控制的不懈追求。

1.4 技术追求的美妙

通过深入分析Mutex的实现和演进,我们可以体验到Go语言设计者在面对并发挑战时的技术追求。从简单的锁机制到复杂的公平性保证,每一步改进都是为了更好地服务于高并发场景。

引言部分详细解释

  • 并发编程****的挑战:并发编程需要处理多个执行流同时访问共享资源的问题。这不仅增加了程序的复杂性,也带来了同步和数据一致性的挑战。

  • Mutex 的角色

    • Mutex提供了一种机制,通过锁定和解锁操作来控制对共享资源的访问。
    • 在Go中,sync.Mutex是最常用的同步原语之一,它通过Lock()Unlock()方法来管理对共享资源的访问。
  • Mutex 设计的演进

    • 最初的Mutex实现非常简单,只使用一个标志位来表示锁的状态。
    • 随着时间的推移,为了提高性能和公平性,Mutex的实现逐渐增加了新的功能,如饥饿模式和自旋锁等。
  • 技术追求的美妙

    • 通过学习Mutex的实现细节,我们可以更深入地理解Go语言的并发模型和同步机制。
    • 这种技术追求不仅提升了语言的性能,也为用户提供了更强大、更灵活的工具来构建并发程序。

引言部分注意点

  • 理解并发编程的基本概念和挑战是学习Mutex实现的基础。
  • 注意Mutex在Go并发模型中的作用,以及它如何帮助避免数据竞争和其他同步问题。
  • 随着学习的深入,要关注Mutex实现的演进,理解每一代设计背后的动机和解决的问题。

引言部分流程图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这个流程图概括了从并发编程的挑战到Mutex设计演进的逻辑流程,以及最终实现的性能与公平性的平衡。

2. Mutex 架构演进

2.1 初版 Mutex 设计
  • 目的:创建一个基础的互斥锁,确保在任意时刻只有一个goroutine可以进入临界区。
  • 结构:使用一个int32类型的key作为锁状态标识。
  • 简单性:实现简单,易于理解,但缺乏性能优化。
2.2 性能优化 - 给新人机会
  • 目的:提高锁的吞吐量,减少上下文切换,允许新来的goroutine有获取锁的机会。
  • 结构变更:引入state int32,用位字段表示锁的不同状态。
  • CAS操作:使用CAS操作更新state,增加了对新来goroutine的友好性。
2.3 进一步优化 - 多给些机会
  • 自旋(Spinning):在某些情况下,让goroutine自旋等待而不是立即进入睡眠状态,以提高锁的响应速度。
  • 问题:增加了代码的复杂性,且自旋过多会消耗CPU资源。
2.4 公平性保障 - 解决饥饿
  • 饥饿问题:长时间等待的goroutine可能永远无法获取锁。
  • 饥饿模式:引入饥饿模式,超过一定时间未获取锁的goroutine会被提升为高优先级。
2.1 初版 Mutex 详解
  • 结构体定义

    • type Mutex struct {
          key int32
      }
      
  • Lock流程

    • 使用CAS检查key是否为0。
    • 如果是0,将key设置为1,表示锁被当前goroutine持有。
    • 如果不是0,表示锁被其他goroutine持有,当前goroutine将被阻塞。
  • Unlock流程

    • key减1。
    • 如果key减至0,表示没有其他goroutine等待锁,当前goroutine可以释放锁。
2.2 给新人机会详解
  • 结构体变更
    • type Mutex struct {
          state int32 // 使用state代替key,包含更多锁状态信息
      }
      
  • Lock流程
    • 通过CAS尝试将state的第一位设置为1,表示锁被持有。
    • 如果设置成功,表示当前goroutine获取了锁。
    • 如果state不为0,表示锁被其他goroutine持有或有goroutine在等待,当前goroutine将自旋或睡眠等待。
2.3 多给些机会详解
  • 自旋逻辑
    • Lock()中增加自旋,如果锁短时间内被释放,自旋的goroutine可以直接获取锁,而不需要进入睡眠状态。
2.4 解决饥饿详解
  • 饥饿模式
    • 如果一个goroutine等待时间过长,Mutex会将其标记为饥饿状态,提高其获取锁的优先级。
2.5 流程图分析

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这个流程图展示了Mutex在不同阶段的获取流程,包括CAS操作、自旋等待和睡眠等待。通过这个流程图,可以更清晰地理解Mutex在不同情况下的行为。

2.6 注意点
  • 在学习Mutex的演进时,要特别注意每个阶段引入的新特性和解决的问题。
  • 理解位运算在表示多个状态时的高效性,以及CAS操作在无锁编程中的重要性。
  • 认识到自旋和睡眠等待的权衡,以及它们对性能和资源利用的影响。
  • 饥饿模式的引入是公平性的重要保障,但也要注意它可能带来的性能影响。

3. Mutex 核心实现分析

3.1 CAS 操作详解
  • CAS (Compare-And-Swap) 是一种用于实现无锁编程的原子操作。它比较内存中的值是否与预期值相同,如果相同,则将其更新为新值,整个过程是原子的。

  • CAS 在 Mutex 中的作用

    • 确保在多goroutine环境中,锁的状态能够安全地更新,避免数据竞争。
  • CAS 实现示例

    • if atomic.CompareAndSwapInt32(&m.state, old, new) {
          // 操作成功,old值与内存中的值相同,现已更新为new值
      } else {
          // 操作失败,old值与内存中的值不同,说明有其他goroutine已修改了state
      }
      
3.2 信号量(Semaphore)详解
  • 信号量 是一种用于控制多个goroutine对共享资源访问的同步机制。

  • 信号量****在 Mutex 中的作用

    • 当一个goroutine尝试获取一个已被其他goroutine持有的锁时,该goroutine会被挂起(阻塞),此时使用信号量来管理goroutine的挂起和唤醒。
  • 信号量****操作示例

    • // 当前goroutine尝试获取锁
      if !atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
          runtime.Semacquire(&m.sema) // 阻塞等待
      }
      
3.3 位操作详解
  • 位操作 是对整数的二进制位进行操作的技术,可以用来在有限的存储空间内表示多个状态。

  • Mutex 中的位操作

    • state 字段通过位字段来表示锁的不同状态,例如是否被锁定、是否有goroutine被唤醒、是否处于饥饿模式等。
  • 位操作示例

    • type Mutex struct {
          state int32 // 位字段,包含多种锁状态信息
      }
      const (
          mutexLocked = 1 << 0  // 锁被持有
          mutexWoken  = 1 << 1  // 有goroutine被唤醒
          mutexStarving = 1 << 2 // 锁处于饥饿模式
          // ... 其他位操作
      )
      
3.4 Mutex 状态字段(state)的位字段分析
  • state 字段

    • 通过位操作,state 字段能够同时表示锁的状态和其他相关信息。
  • 位字段示例

    • var state int32
      locked := state & mutexLocked > 0       // 检查是否锁定
      woken := state & mutexWoken > 0         // 检查是否有唤醒的goroutine
      waiters := state >> mutexWaiterShift      // 获取等待锁的goroutine数量
      
3.5 流程图分析 - CAS 操作流程

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这个流程图展示了在Mutex的获取过程中,CAS操作的流程和可能的分支,包括自旋等待和信号量阻塞等待。

3.6 注意点
  • CAS操作:理解CAS操作的重要性,它是实现无锁编程的核心。
  • 信号量****使用:注意信号量在Mutex实现中的作用,以及它如何帮助管理goroutine的阻塞和唤醒。
  • 位操作:掌握位操作的技巧,理解如何在有限的空间内使用位字段表示更多的状态信息。
  • state字段:深入理解state字段的每一位的含义,以及如何通过位操作来检查和更新这些状态。

通过深入分析Mutex的核心实现,我们可以更好地理解Go语言中的并发机制,以及如何安全高效地管理共享资源的访问。这些知识对于编写高性能和高可靠性的并发程序至关重要。

4. Mutex 的性能与公平性权衡

4.1 性能优化
  • 性能的重要性:在高并发场景下,性能是关键因素。一个高效的Mutex可以实现快速的锁获取和释放,减少goroutine的阻塞时间。

  • 性能优化措施

    • 自旋锁:在某些情况下,如果锁持有时间短,让等待的goroutine自旋而不是立即睡眠,可以减少上下文切换的开销。
    • 优先级调整:新来的goroutine在某些条件下可以优先获取锁,以提高系统的响应性。
4.2 公平性保障
  • 公平性的定义:确保所有等待锁的goroutine最终都能按顺序获得锁的机会。

  • 公平性问题:在高性能优化中,如果不考虑公平性,可能导致某些goroutine长时间无法获取锁,即“饥饿”。

  • 饥饿模式:引入饥饿模式,确保长时间等待的goroutine能够获得锁,避免饥饿现象。

4.3 性能与公平性的平衡
  • 平衡的挑战:在提高性能的同时保证公平性是一个复杂的权衡问题。

  • 平衡策略

    • 动态调整:根据系统的运行状态动态调整策略,如在检测到饥饿现象时,适时进入饥饿模式。
    • 阈值控制:设置时间阈值,超过该阈值的等待goroutine将被赋予更高的锁获取优先级。
4.4 性能优化详解
  • 自旋锁的适用场景

    • 当锁持有时间非常短,且CPU资源相对充足时,自旋锁可以减少线程的睡眠和唤醒开销。
  • 自旋锁的实现

    • for {
          if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
              return // 成功获取锁
          }
          if !runtime_canSpin() {
              break // 不能自旋,进入等待状态
          }
          runtime_doSpin() // 自旋等待
      }
      
4.5 公平性保障详解
  • 饥饿模式的触发条件

    • 当一个goroutine等待锁的时间超过预设的阈值时,该goroutine将被标记为饥饿状态。
  • 饥饿模式的实现

    • if waitStartTime != 0 && runtime_nanotime()-waitStartTime > starvationThresholdNs {
          new |= mutexStarving // 设置饥饿状态
      }
      
4.6 流程图分析 - 性能与公平性权衡

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这个流程图展示了在请求锁的过程中,如何根据锁的状态和等待时间来权衡性能和公平性。

4.7 注意点
  • 自旋锁:理解自旋锁的适用场景和潜在风险,如过度自旋可能导致CPU资源浪费。
  • 饥饿模式:注意饥饿模式的触发条件和实现方式,以及它如何帮助避免长时间等待的goroutine被饿死。
  • 权衡策略:学习如何在不同的场景下根据系统的实际运行状态动态调整性能和公平性的权衡策略。

通过深入理解Mutex在性能和公平性之间的权衡,我们可以更好地把握并发编程中的同步机制,编写出既高效又公平的并发程序。这些知识对于处理复杂的并发问题和优化系统性能至关重要。

5. Mutex 的设计哲学

5.1 稳定性与兼容性
  • 稳定性:Go语言强调API的稳定性,确保随着语言的发展,现有的代码库依然能够正常运行。
  • 兼容性:新版本的Go语言和标准库保持向下兼容,使得开发者可以安心地升级Go版本,而不必担心现有代码的兼容性问题。
5.2 扩展性与前瞻性
  • 扩展性:Go的设计哲学强调了代码的可扩展性,使得在未来添加新特性时,可以最小化对现有代码的修改。
  • 前瞻性:Go的设计者在设计之初就考虑到了未来可能的需求,使得标准库能够适应不断变化的编程模式和技术发展。
5.3 简洁性与复杂性的平衡
  • 简洁性:Go语言的设计哲学倾向于简洁明了的代码,使得开发者易于理解和使用。
  • 复杂性的平衡:尽管简洁性是目标,但在并发编程等领域,为了实现高性能和高可靠性,必要的复杂性是不可避免的。Go的设计者在简洁性和复杂性之间寻求平衡。
5.4 性能与公平性的权衡
  • 性能优先:在某些场景下,性能是首要考虑的因素,Go的设计者提供了性能优先的解决方案。
  • 公平性保障:在其他场景下,公平性更为重要,Go的设计者同样提供了保障公平性的机制,如Mutex的饥饿模式。
5.5 设计哲学的实现
  • 接口的稳定性:尽管Mutex的内部实现随着版本的更新而发生了变化,但其对外的接口保持稳定,这体现了Go语言对接口稳定性的重视。

  • 适应性设计:Go的设计者在设计Mutex时,不仅考虑了当前的需求,还预见了未来可能的变化,使得Mutex能够适应不同的并发场景。

  • 逐步优化:从简单的互斥锁到复杂的饥饿模式,Go的设计者逐步优化Mutex的实现,以满足日益增长的性能和公平性需求。

5.6 流程图分析 - 设计哲学的体现

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这个流程图展示了Go的设计哲学如何在Mutex的设计中得到体现,从简洁性、扩展性到性能与公平性的综合考量。

5.7 注意点
  • 稳定性和****兼容性:理解Go语言对稳定性和兼容性的重视,以及它们对长期维护和升级的重要性。
  • 扩展性和前瞻性:认识到在设计初期考虑未来需求的重要性,以及它如何帮助应对技术的发展。
  • 简洁性与复杂性的平衡:学会在设计中寻求简洁性,同时接受并管理必要的复杂性,以实现更全面的功能。
  • 性能与公平性的权衡:掌握如何在不同的场景下根据实际需求做出合理的权衡。

通过深入理解Go的设计哲学,并将其应用于Mutex的设计,我们可以更好地把握如何在编写并发程序时做出合理的设计决策,以及如何在保证代码简洁性的同时,实现高性能和高可靠性。

6. 思考题深入解析

6.1 Mutex 的 state 字段
  • state 字段的作用state 字段是 Mutex 结构体中的核心,通过位操作存储了锁的多种状态信息。

  • state 字段的组成

    • mutexLocked:表示锁是否被持有。
    • mutexWoken:表示是否有等待的 goroutine 被唤醒。
    • mutexStarving:表示锁是否处于饥饿模式。
    • mutexWaiterShift:表示等待锁的 goroutine 数量。
  • state 字段的位操作示例

    • const (
          mutexLocked = 1 << iota // mutex is locked
          mutexWoken
          mutexStarving
          mutexWaiterShift = iota
      )
      
  • 位操作的解释

    • mutexLocked:锁被持有时,该位为1。
    • mutexWoken:有等待的 goroutine 被唤醒时,该位为1。
    • mutexStarving:锁处于饥饿模式时,该位为1。
    • mutexWaiterShift:表示等待 goroutine 数量的位移量。
6.2 goroutine 等待数量限制
  • 等待数量的限制state 字段中用于表示等待 goroutine 数量的位数是有限的,因此等待数量有一个上限。

  • 计算等待数量的示例

    • waiters := m.state >> mutexWaiterShift
      
  • 等待数量上限的计算

    • 假设 mutexWaiterShift 占用了3位,则等待 goroutine 数量的位数为 32 - 3 = 29 位。
    • 因此,最大等待数量为 2^29 - 1
6.3 流程图分析 - state 字段的位操作

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这个流程图展示了如何通过检查 state 字段的位操作来处理锁的获取和释放,以及如何处理饥饿模式。

6.4 注意点
  • 位操作的重要性:理解位操作在 Mutex 实现中的作用,特别是在表示多个状态时的高效性。
  • 等待数量的限制:认识到等待 goroutine 数量的限制,并理解其对系统设计的影响。
  • 饥饿模式的触发:理解饥饿模式的触发条件和处理逻辑,以及它如何影响锁的公平性。

通过深入分析 Mutexstate 字段和相关的位操作,我们可以更好地理解 Go 语言中的并发机制,以及如何安全高效地管理共享资源的访问。这些知识对于编写高性能和高可靠性的并发程序至关重要。

6.5 思考题答案
  1. Mutex 的 state 字段有几个意义

    1. 锁是否被持有(mutexLocked)。
    2. 是否有等待的 goroutine 被唤醒(mutexWoken)。
    3. 锁是否处于饥饿模式(mutexStarving)。
    4. 等待锁的 goroutine 数量(由 mutexWaiterShift 表示)。
  2. 等待一个 Mutex 的 goroutine 数最大是多少

    1. 假设 mutexWaiterShift 占用了3位,则最大等待数量为 2^(32-3) - 1,即大约 1073741822。

通过这些详细的解析和示例,您可以更深入地理解 Mutex 的内部机制,以及如何在实际编程中应用这些知识。

7. 结论与展望

7.1 结论

在深入分析了Go语言中Mutex的实现和演进过程后,我们可以得出以下结论:

  • 设计复杂性:尽管Mutex的初始设计非常简单,但随着对性能和公平性要求的提高,其实现变得越来越复杂。这种复杂性是必要的,以满足高并发环境下的需求。
  • 性能与公平性Mutex的设计不断在性能和公平性之间寻求平衡。通过引入自旋锁、饥饿模式等机制,Mutex能够在不同的场景下提供合理的锁获取策略。
  • 稳定性与****兼容性:Go语言的设计哲学强调了API的稳定性和向下兼容性。这使得开发者可以安心地使用Mutex,而不必担心未来的语言更新会破坏现有的代码。
7.2 展望

随着并发编程的不断发展,我们可以预见Mutex及其相关同步机制可能会继续演进。以下是一些可能的发展方向:

  • 更细粒度的控制:未来可能会有更细粒度的锁控制机制,允许开发者根据具体的并发模式和资源访问模式来定制锁的行为。
  • 适应性同步:随着机器学习技术的发展,可能会有自适应的同步机制,能够根据程序的运行状态动态调整同步策略。
  • 跨语言同步:随着多语言混合编程的普及,可能会有跨语言的同步机制,允许不同语言编写的代码共享同步原语。
7.3 流程图分析 - Mutex 的演进

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这个流程图展示了从最初的Mutex设计到当前版本,再到未来可能的发展方向的演进路径。

7.4 注意点
  • 理解演进:理解Mutex设计演进的历史和背后的动机,有助于更好地把握并发编程的复杂性。
  • 关注未来:虽然当前的Mutex设计已经相当成熟,但技术的不断进步意味着未来可能会有新的需求和挑战。
  • 实践应用:将学到的知识应用到实际的并发编程中,不断实践和优化,是提高并发编程能力的关键。
7.5 个人视角

作为个人笔记,我认为深入理解Mutex的实现和设计哲学是非常重要的。这不仅有助于我们在实际编程中更好地使用Mutex,也为我们提供了一个思考并发编程问题的框架。以下是我个人的一些观点:

  • 深入理解:深入理解Mutex的内部实现,包括CAS操作、信号量、位操作等,是掌握并发编程的关键。
  • 实践应用:理论知识需要通过实践来巩固。在实际项目中使用Mutex,并观察其行为,是提高理解的最好方式。
  • 持续学习:并发编程是一个不断发展的领域,持续学习新的技术和方法是必要的。

通过这些笔记,我希望能够帮助自己和其他开发者更好地理解和使用Go语言中的并发原语,编写出更高效、更可靠的并发程序。

8. 附录

8.1 Mutex 相关代码片段及注释
8.1.1 初版 Mutex 实现
type Mutex struct {
    key int32 // 锁是否被持有的标识
    sema int32 // 信号量专用,用以阻塞/唤醒goroutine
}

func (m *Mutex) Lock() {
    if xadd(&m.key, 1) == 1 { // 标识加1,如果等于1,成功获取到锁
        return
    }
    semacquire(&m.sema) // 否则阻塞等待
}

func (m *Mutex) Unlock() {
    if xadd(&m.key, -1) == 0 { // 将标识减去1,如果等于0,则没有其它等待者
        return
    }
    semrelease(&m.sema) // 唤醒其它阻塞的goroutine
}
  • 注释:初版 Mutex 使用 key 来标识锁的状态,并通过 sema 信号量来控制 goroutine 的阻塞和唤醒。
8.1.2 给新人机会的 Mutex 实现
type Mutex struct {
    state int32
    sema uint32
}

const (
    mutexLocked = 1 << iota // mutex is locked
    mutexWoken
    mutexWaiterShift = iota
)

func (m *Mutex) Lock() {
    // Fast path: 幸运case,能够直接获取到锁
    if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
        return
    }
    awoke := false
    for {
        old := m.state
        new := old | mutexLocked // 新状态加锁
        if old&mutexLocked != 0 {
            new = old + 1 << mutexWaiterShift //等待者数量加一
        }
        if awoke {
            // goroutine是被唤醒的,新状态清除唤醒标志
            new &^= mutexWoken
        }
        if atomic.CompareAndSwapInt32(&m.state, old, new) { //设置新状态
            if old&mutexLocked == 0 { // 锁原状态未加锁
                break
            }
            runtime.Semacquire(&m.sema) // 请求信号量
            awoke = true
        }
    }
}
  • 注释:在这个阶段,Mutex 的 state 字段被引入,用以更细致地控制锁的状态和等待 goroutine 的管理。
8.1.3 解决饥饿的 Mutex 实现
const (
    mutexLocked = 1 << iota // mutex is locked
    mutexWoken
    mutexStarving // 从state字段中分出一个饥饿标记
    mutexWaiterShift = iota
    starvationThresholdNs = 1e6 // 饥饿阈值
)

func (m *Mutex) Lock() {
    // Fast path: 幸运之路,一下就获取到了锁
    if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
        return
    }
    // Slow path:缓慢之路,尝试自旋竞争或饥饿状态下饥饿goroutine竞争
    m.lockSlow()
}

func (m *Mutex) lockSlow() {
    var waitStartTime int64
    starving := false // 此goroutine的饥饿标记
    awoke := false // 唤醒标记
    iter := 0 // 自旋次数
    for {
        old := m.state
        new := old
        if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
            if awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 {
                new |= mutexWoken
            } else {
                runtime_doSpin()
                iter++
                continue
            }
        }
        new |= mutexLocked
        if old&mutexStarving == 0 {
            new += 1 << mutexWaiterShift // waiter数量加1
        }
        if starving && old&mutexLocked != 0 {
            new |= mutexStarving // 设置饥饿状态
        }
        if awoke {
            new &^= mutexWoken // 清除唤醒标记
        }
        if atomic.CompareAndSwapInt32(&m.state, old, new) {
            if old&(mutexLocked|mutexStarving) == 0 {
                break // locked the mutex with CAS
            }
            runtime_SemacquireMutex(&m.sema, waitStartTime != 0, 1)
            awoke = true
            iter = 0
            waitStartTime = runtime_nanotime()
            starving = waitStartTime != 0 && runtime_nanotime()-waitStartTime > starvationThresholdNs
        } else {
            old = m.state
        }
    }
}
  • 注释:在这个阶段,引入了饥饿模式,确保长时间等待的 goroutine 能够公平地获取锁。
8.2 位操作示例及解释
8.2.1 位操作基础

位操作是一种在二进制级别上对数据进行操作的技术,非常适合用于在有限的空间内存储多个状态信息。

const (
    mutexLocked = 1 << iota // mutex is locked
    mutexWoken
    mutexStarving
    mutexWaiterShift = iota
)

// 假设 state 的初始值为 0
state := 0
state |= mutexLocked      // state 现在为 1 (0b0001)
state |= 1 << mutexWoken   // state 现在为 3 (0b0011)
state |= mutexStarving    // state 现在为 7 (0b0111)
  • 注释:通过位或操作(|=),可以在同一变量中设置多个状态标志。
8.2.2 检查和修改状态
locked := state&mutexLocked > 0       // 检查是否锁定
woken := state&mutexWoken > 0         // 检查是否有唤醒的goroutine
starving := state&mutexStarving > 0    // 检查是否处于饥饿模式
waiters := state >> mutexWaiterShift  // 获取等待锁的goroutine数量
  • 注释:通过位与操作(&)和位移操作(>>),可以检查和提取状态信息。
8.3 个人学习总结
  • 深入理解 Mutex:通过学习 Mutex 的实现,我更深入地理解了并发编程中的同步机制和挑战。
  • 性能与公平性的权衡:理解了在设计并发系统时,性能和公平性之间的权衡是不可避免的。
  • 代码实践的重要性:理论知识需要通过实践来巩固,实际编写和测试并发代码是提高理解的关键。

通过这些笔记,我希望能够更好地理解和应用 Go 语言中的并发原语,编写出更高效、更可靠的并发程序。同时,这些知识也为我提供了一个思考并发编程问题的框架,帮助我在面对并发挑战时做出更明智的设计决策。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1935795.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【ffmpeg】一篇文章搞定YUV

文章目录 前言YUV是什么&#xff1f;YUV的用途YUV采样格式采样格式是什么YUV采样格式有哪些YUV采样格式的区别 YUV与RGBRGB 颜色空间YUV 颜色空间RGB 与 YUV 的比较RGB 转 YUV 公式YUV 转 RGB 公式注意事项 YVU数据计算通用公式4:4:4 采样格式4:2:2 采样格式4:2:0 采样格式实例…

win10删除鼠标右键选项

鼠标右键菜单时&#xff0c;发现里面的选项特别多&#xff0c;找一下属性&#xff0c;半天找不到。删除一些不常用的选项&#xff0c;让右键菜单变得干净整洁。 1、按下键盘上的“winR”组合按键&#xff0c;调出“运行”对话框&#xff0c;输入“regedit”命令&#xff0c;点击…

达梦数据库的系统视图v$rapply_log_task

达梦数据库的系统视图v$rapply_log_task 达梦数据库的V$RAPPLY_LOG_TASK视图是一个动态性能视图&#xff0c;主要用于在备库上查询。该视图需要在备库上查询&#xff08;DMDSC 备库需要在控制节点&#xff08;重演节点&#xff09;上查询&#xff09;&#xff0c;用于查询备库…

专业PDF编辑工具:Acrobat Pro DC 2024.002.20933绿色版,提升你的工作效率!

软件介绍 Adobe Acrobat Pro DC 2024绿色便携版是一款功能强大的PDF编辑和转换软件&#xff0c;由Adobe公司推出。它是Acrobat XI系列的后续产品&#xff0c;提供了全新的用户界面和增强功能。用户可以借助这款软件将纸质文件转换为可编辑的电子文件&#xff0c;便于传输、签署…

RocketMQ单结点安装/Dashboard安装

目录 1.安装NameServer 2.安装Broker 3.使用自带工具测试数据发送 4.使用DashBoard进行查看 5.关闭相关设备 前置条件&#xff1a;两台虚拟机CentOS Linux release 7.5.1804(ps:当然也可以都部署在一台机器上) RocketMq属于天生集群。需要同时启动nameServer和Broker进行…

STP(802.1D)

802.1D&#xff1a;也成为生成树协议&#xff08;Spanning Tree Protocol&#xff0c;STP&#xff09;&#xff0c;是一种工作在数据链路层的通信协议 作用&#xff1a;防止交换机冗余链路产生的环路&#xff0c;确保以太网中无环路的逻辑拓扑结构&#xff0c;从而避免广播风暴…

MacBook电脑远程连接Linux系统的服务器方法

一、问题简介 Windows 操作系统的电脑可使用Xshell等功能强大的远程连接软件。通过连接软件&#xff0c;用户可以在一台电脑上访问并控制另一台远程计算机。这对于远程技术支持、远程办公等场景非常有用。但是MacBook电脑的macOS无法使用Xshell。 在Mac上远程连接到Windows服…

昇思25天学习打卡营第18天 | 基于MindSpore的GPT2文本摘要

昇思25天学习打卡营第18天 | 基于MindSpore的GPT2文本摘要 文章目录 昇思25天学习打卡营第18天 | 基于MindSpore的GPT2文本摘要数据集创建数据集数据预处理Tokenizer 模型构建构建GPT2ForSummarization模型动态学习率 模型训练模型推理总结打卡 数据集 实验使用nlpcc2017摘要数…

详解MLOps,从Jupyter开发到生产部署

大家好&#xff0c;Jupyter notebook 是机器学习的便捷工具&#xff0c;但在应用部署方面存在局限。为了提升其可扩展性和稳定性&#xff0c;需结合DevOps和MLOps技术。通过自动化的持续集成和持续交付流程&#xff0c;可将AI应用高效部署至HuggingFace平台。 本文将介绍MLOps…

网安播报|Python 的 GitHub 核心资源库 token 意外曝光,风险巨大

1、Python 的 GitHub 核心资源库 token 意外曝光&#xff0c;风险巨大 网络安全专家发现了意外泄露的 GitHub token&#xff0c;能以最高权限访问 Python 语言、Python 软件包索引&#xff08;PyPI&#xff09;和 Python 软件基金会&#xff08;PSF&#xff09;存储库。如果该 …

如何确保微型导轨电能表的精准计量?

微型导轨电能表是一种小型化的电表&#xff0c;通常用于精密仪器和设备中。采用模数化设计&#xff0c;精确度高&#xff0c;具有体积小&#xff0c;易安装&#xff0c;易组装等优点。易于实现终端照明电能计量&#xff0c;便于照明系统加装电度表的改造。 对于用户来说&#x…

2024北京国际智能工厂及自动化展览会亮点前瞻

随着“工业创新&#xff0c;智造未来”的浪潮席卷而来&#xff0c;2024年度北京国际智能工厂及自动化与工业装配展览会定于8月1日至3日在中国国际展览中心&#xff08;顺义新馆&#xff09;盛大开幕。本次展会汇聚了智能制造与自动化技术的最新成果&#xff0c;通过三展联动的创…

ozon计算器5.0版本,ozon计算器5.0定价产品价格

在跨境电商的浩瀚星海中&#xff0c;俄罗斯Ozon电商平台以其庞大的市场规模和快速增长的势头&#xff0c;成为了众多卖家竞相布局的蓝海。然而&#xff0c;在这片充满机遇的土地上&#xff0c;如何精准定价&#xff0c;确保利润最大化&#xff0c;同时又能吸引消费者&#xff0…

【Git分支管理】分支策略 | Bug分支

目录 1.分支策略 2.特殊场景-Bug分支 2.1 master出现bug ​2.2 dev2正在开发☞stash区域 2.3 dev2正在开发master出现bug 2.3.1 fix_bug修复bug和master合并 2.3.2 dev2分支开发完和master合并 合并冲突&#xff1a;merge☞手动解决☞提交没有合并冲突&#xff1a;mer…

基于Go语言快速开发抖音小程序 提高性能、效率和降低成本开发框架 让开发变得极简单 开箱即用省时又省钱

前言 用Go语言Arco Design集成后台框架&#xff0c;我们把日常开发常用的基础开发成基础包&#xff0c;等到有项目时安装、下载基础代码就可以马上开发业务&#xff0c;您可以快速搭建好抖音小程序应用开发&#xff0c;为大家搭建抖音后台管理、抖音原生开发模板、小程序登录、…

大模型-Bert+PET实战

PET&#xff08;Pattern-Exploiting Training&#xff09; 背景&#xff1a;预训练语言模型&#xff08;比如BERT&#xff09;知识全面&#xff0c;但是没有针对下游任务做针对训练&#xff0c;所以效果一般&#xff0c;所以需要根据任务做微调。 核心思想&#xff1a;根据先…

langchain 简介

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 LangChain 是一个用于语言模型和应用程序开发的框架&#xff0c;它提供了一系列工具和组件&#xff0c; 帮助开发者更轻松地构建基于大型…

【JVM基础02】——组成-程序计数器解读

目录 1- 引言&#xff1a;程序计数器1-1 程序计数器是什么&#xff1f;为什么用程序计数器&#xff1f;(What)(Why) 2- 核心&#xff1a;程序计数器的原理&#xff08;How&#xff09;2-1 使用 javap 查看程序计数器的作用2-2 多线程下程序计数器原理举例 3- 小结&#xff1a;什…

Linux HOOK机制与Netfilter HOOK

一. 什么是HOOK&#xff08;钩子&#xff09; 在计算机中&#xff0c;基本所有的软件程序都可以通过hook方式进行行为拦截&#xff0c;hook方式就是改变原始的执行流。 二. Linux常见的HOOK方式 1、修改函数指针。 2、用户态动态库拦截。 ①利用环境变量LD_PRELOAD和预装载机…

Calibre:soft check

我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧? 拾陆楼知识星球入口 soft check检查在lvs检查中属于必看的类型,往往是因为衬底没有硬连接,接pg stripe造成的 下图是一个soft check的错误报告,重要信息有两个: 1)问题在ntap上,也就是着重检查power pin相关的连…