LiSheng's blog LiSheng's blog
首页
笔记
个人简历
随笔集
GitHub (opens new window)
首页
笔记
个人简历
随笔集
GitHub (opens new window)
  • golang

    • 并发编程

      • GMP并发模型
      • 锁相关
        • groutine并发相关
        • go如何实现原子操作
      • 内存管理

      • 数组和切片的区别
      • new和make
      • go defer
      • context
      • channel
      • go map
      • interface
      • 对象系统
      • rune 类型
      • 字符串拼接的几种方式
    • cplus

    • leetcode

    • 存储技术

    • 分布式系统

    • 计算机网络

    • Linux操作系统

    • Redis

    • 其他

    • 笔记
    • golang
    • 并发编程
    lisheng
    2024-09-10
    目录

    锁相关

    1、除了 mutex 以外还有那些方式安全读写共享变量? 2、Go 如何实现原子操作? 3、Mutex 是悲观锁还是乐观锁?悲观锁、乐观锁是什么? 4、Mutex 有几种模式? 5、goroutine 的自旋占用资源如何解决

    通常涉及 sync.Mutex、sync.RWMutex 等常见的锁类型,以及与锁相关的最佳实践、问题排查等

    # 1. Mutex 的基本概念

    • 问题: 什么是 sync.Mutex?它是如何工作的?
    • 回答要点:
      • sync.Mutex 是 Go 标准库中的一种互斥锁,用于保护共享资源的访问。
      • 通过 Lock 方法加锁,Unlock 方法解锁。在加锁后,其他 Goroutine 需要等到解锁才能继续访问共享资源。

    # 2. Mutex 的使用示例

    • 问题: 请写出使用 sync.Mutex 来保护一个共享变量的代码示例。
    • 回答要点:
      var mu sync.Mutex
      var 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("Final count:", count)
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21

    # 3. 死锁的概念与避免

    • 问题: 什么是死锁?在使用锁时如何避免死锁?
    • 回答要点:
      • 死锁是指两个或多个 Goroutine 因相互等待对方持有的锁,而陷入无限期的等待状态,导致程序无法继续执行。
      • 避免死锁的方法包括:避免嵌套锁、尝试使用定时锁、在相同顺序下加锁、最小化锁持有的时间。

    # 4. sync.RWMutex 的使用场景

    • 问题: 什么是 sync.RWMutex?它和 sync.Mutex 有什么区别?适用于什么场景?
    • 回答要点:
      • sync.RWMutex 是一种读写锁,允许多个 Goroutine 同时读取,但在写入时会独占锁。
      • 与 sync.Mutex 不同,RWMutex 提供了 RLock 和 RUnlock 方法,用于读操作,加速了并发读多于写的场景。
      • 适用于读多写少的场景,如缓存系统。

    # 5. 锁的竞态条件

    • 问题: 什么是竞态条件?如何使用锁来避免竞态条件?
    • 回答要点:
      • 竞态条件是指多个 Goroutine 同时访问和修改共享数据时,由于访问顺序不确定而引发的数据不一致问题。
      • 使用 sync.Mutex 或 sync.RWMutex 来保护共享资源,确保同一时刻只有一个 Goroutine 能修改数据,从而避免竞态条件。

    # 6. defer 与锁的配合使用

    • 问题: 为什么推荐使用 defer 来解锁?请给出一个例子。
    • 回答要点:
      • 使用 defer 可以确保锁在函数退出时一定会被释放,避免因函数中途返回或发生错误而导致的锁泄漏。
      • 示例:
        mu.Lock()
        defer mu.Unlock()
        // critical section
        
        1
        2
        3

    # 7. 锁的嵌套与递归

    • 问题: Go 的 sync.Mutex 是否支持递归锁定?为什么?
    • 回答要点:
      • Go 的 sync.Mutex 不支持递归锁定,即一个 Goroutine 不能对同一把锁进行多次加锁,否则会导致死锁。
      • 这与一些其他编程语言(如 C++ 的 std::recursive_mutex)不同,Go 的锁设计更加简单和直接。

    # 8. 避免锁的性能瓶颈

    • 问题: 在高并发程序中,如何避免因锁的使用而导致的性能瓶颈?
    • 回答要点:
      • 最小化锁的粒度,只锁住关键的代码部分。
      • 使用 sync.RWMutex 来区分读写锁,减少不必要的写锁争用。
      • 考虑使用无锁数据结构(如 sync.Map)或锁分片技术来减小锁的争用。
      • 尽量减少锁的持有时间,避免在持有锁的情况下执行耗时操作。

    # 9. sync.Cond 的使用

    • 问题: 什么是 sync.Cond?它的作用是什么?请给出使用 sync.Cond 实现生产者-消费者模型的示例。
    • 回答要点:
      • sync.Cond 是一种条件变量,可以让一个或多个 Goroutine 等待或广播某个条件的发生。
      • 适用于需要在某个条件满足时通知其他 Goroutine 的场景。
      • 示例:
        var mu sync.Mutex
        cond := sync.NewCond(&mu)
        queue := make([]int, 0)
        
        func produce(val int) {
            mu.Lock()
            queue = append(queue, val)
            cond.Signal()
            mu.Unlock()
        }
        
        func consume() {
            mu.Lock()
            for len(queue) == 0 {
                cond.Wait()
            }
            val := queue[0]
            queue = queue[1:]
            mu.Unlock()
            fmt.Println("Consumed:", val)
        }
        
        func main() {
            go consume()
            produce(1)
        }
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26

    # 10. 锁与原子操作的比较

    • 问题: Go 语言中的锁与原子操作(如 sync/atomic 包中的操作)有何不同?在什么情况下应使用原子操作而不是锁?
    • 回答要点:
      • 锁可以保护复杂的临界区,而原子操作适用于对单个变量的简单读写操作。
      • 原子操作通常比锁开销更小,因为它们不涉及上下文切换和调度器干预。
      • 在高并发环境中,如果只需对整数、布尔值等基本数据类型进行简单的增减或状态检查,原子操作是更好的选择。

    # 11. 锁的使用陷阱

    • 问题: 在使用锁时,有哪些常见的陷阱或错误?
    • 回答要点:
      • 忘记解锁:在使用 Lock 后没有调用 Unlock,导致死锁。
      • 重复解锁:多次调用 Unlock,会引发 panic。
      • 使用锁保护的代码过多,导致性能问题。
      • 在锁保护下调用可能会阻塞的操作(如网络或文件 I/O),导致长时间持有锁,从而降低并发性。

    # 12. 死锁检测

    • 问题: 如何检测和调试 Go 程序中的死锁问题?
    • 回答要点:
      • 使用 Go 的内置工具 go build -race 来检测数据竞争和死锁。
      • 检查程序是否在特定锁上永久阻塞,或使用较长的超时时间来定位死锁发生的 Goroutine。
      • 通过分析 Goroutine 堆栈追踪,找到所有正在等待锁的 Goroutine 以及它们的状态。
    编辑 (opens new window)
    上次更新: 2024/09/13, 11:59:12
    GMP并发模型
    groutine并发相关

    ← GMP并发模型 groutine并发相关→

    最近更新
    01
    ceph分布式存储-对象存储(RGW)搭建
    10-27
    02
    ceph分布式存储-集群客户端连接
    10-27
    03
    ceph分布式存储-管理crushmap
    10-27
    更多文章>
    Theme by Vdoing
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式