go的Mutex实现原理及演进

news/2024/9/5 19:31:24 标签: golang

下面的这个是对于昨天晚上读的几篇关于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.niftyadmin.cn/n/5564755.html

相关文章

AttributeError: ‘WebDriver‘ object has no attribute ‘find_element_by_xpath‘

问题&#xff1a; 跑之前的python爬虫代码的时候报错 AttributeError: WebDriver object has no attribute find_element_by_xpath 源代码&#xff1a; elements self.driver.find_elements_by_xpath("//tbody[starts-with(id,normalthread)]/tr/th/a[1]")原因&…

云计算实训10——frp服务的搭建、ftp服务的搭建

一、frp服务的搭建 frp⽀持 tcp, udp, http, https等 协议 frp的底层----vpn 1.frp的工作原理 服务端运⾏&#xff0c;监听⼀个主端⼝&#xff0c;等待客户端的连接&#xff1b; 客户端连接到服务端的主端⼝&#xff0c;同时告诉服务端要监听的端⼝和转发类型&#xff1b; 服…

spring MVC 简单的案例(2)用户登录

一、用户登录 1&#xff09;前端代码 index.html <!doctype html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport"content"widthdevice-width, user-scalableno, initial-scale1.0, maxim…

大语言模型-文本向量模型评估基准 MTEB

MTEB&#xff08;Massive Text Embedding Benchmark&#xff09; 涵盖112种语言的58个数据集&#xff0c;包含如下8种任务。 1、双语文本挖掘&#xff08;Bitext Mining&#xff09; 任务目标&#xff1a; 在双语语料库中识别语义等价的句子对。 任务描述&#xff1a; 输入…

vue2关于Object.defineProperty实现响应式

实现步骤&#xff1a; 1. 初始化阶段 当 Vue 实例化时&#xff0c;会遍历data 选项中的属性&#xff0c;并使用 Object.defineProperty 将它们转换为 getter 和 setter。这样一来&#xff0c;每当访问或修改这些属性时&#xff0c; Vue就能捕获到这些操作&#xff0c;从而实现…

Android构建任务assemble、bundle、compile、package、install

1. assemble 开头的任务&#xff1a; assembleDebug&#xff1a;构建 debug 版本的 APK 文件。assembleRelease&#xff1a;构建 release 版本的 APK 文件。assembleAndroidTest&#xff1a;构建测试 APK 文件&#xff0c;用于测试应用程序。assembleAndroidTestDebug&#xf…

AP ERP与汉得SRM系统集成案例(制药行业)

一、项目环境 江西某医药集团公司&#xff0c;是一家以医药产业为主营、资本经营为平台的大型民营企业集团。公司成立迄今&#xff0c;企业经营一直呈现稳健、快速发展的态势&#xff0c; 2008 年排名中国医药百强企业前 20 强&#xff0c;2009年集团总销售额约38亿元人民币…

价格较低,功能最强?OpenAI 推出 GPT-4o mini,一个更小、更便宜的人工智能模型

OpenAI美东时间周四推出“GPT-4o mini”&#xff0c;入局“小而精”AI模型竞争&#xff0c;称这款新模型是“功能最强、成本偏低的模型”&#xff0c;计划今后整合图像、视频、音频到这个模型中。 OpenAI表示&#xff0c;GPT-4o mini 相较于 OpenAI 目前最先进的 AI 模型更加便…