• 面试官:Go 如何实现可重入锁


    大家好,我是木川

    一、什么是可重入锁

    可重入锁又称为递归锁,是指在同一个线程在外层方法获取锁的时候,在进入该线程的内层方法时会自动获取锁,不会因为之前已经获取过还没释放再次加锁导致死锁

    Go 里面的 Mutex 不是可重入的锁。Mutex 的实现中没有记录哪个 goroutine 拥有这把锁。理论上,任何 goroutine 都可以随意地 Unlock 这把锁,所以没办法计算重入条件,并且Mutex 重复Lock会导致死锁。

    二、如何实现可重入锁

    实现一个可重入锁需要这 2 点:

    • 记住持有锁的线程

    • 统计重入的次数

    1. package main
    2. import (
    3.  "bytes"
    4.  "fmt"
    5.  "runtime"
    6.  "strconv"
    7.  "sync"
    8.  "sync/atomic"
    9. )
    10. type ReentrantLock struct {
    11.  sync.Mutex
    12.  recursion int32 // 这个goroutine 重入的次数
    13.  owner     int64 // 当前持有锁的goroutine id
    14. }
    15. // Get returns the id of the current goroutine.
    16. func GetGoroutineID() int64 {
    17.  var buf [64]byte
    18.  var s = buf[:runtime.Stack(buf[:], false)]
    19.  s = s[len("goroutine "):]
    20.  s = s[:bytes.IndexByte(s, ' ')]
    21.  gid, _ := strconv.ParseInt(string(s), 1064)
    22.  return gid
    23. }
    24. func NewReentrantLock() sync.Locker {
    25.  res := &ReentrantLock{
    26.   Mutex:     sync.Mutex{},
    27.   recursion: 0,
    28.   owner:     0,
    29.  }
    30.  return res
    31. }
    32. // ReentrantMutex 包装一个Mutex,实现可重入
    33. type ReentrantMutex struct {
    34.  sync.Mutex
    35.  owner     int64 // 当前持有锁的goroutine id
    36.  recursion int32 // 这个goroutine 重入的次数
    37. }
    38. func (m *ReentrantMutex) Lock() {
    39.  gid := GetGoroutineID()
    40.  // 如果当前持有锁的goroutine就是这次调用的goroutine,说明是重入
    41.  if atomic.LoadInt64(&m.owner) == gid {
    42.   m.recursion++
    43.   return
    44.  }
    45.  m.Mutex.Lock()
    46.  // 获得锁的goroutine第一次调用,记录下它的goroutine id,调用次数加1
    47.  atomic.StoreInt64(&m.owner, gid)
    48.  m.recursion = 1
    49. }
    50. func (m *ReentrantMutex) Unlock() {
    51.  gid := GetGoroutineID()
    52.  // 非持有锁的goroutine尝试释放锁,错误的使用
    53.  if atomic.LoadInt64(&m.owner) != gid {
    54.   panic(fmt.Sprintf("wrong the owner(%d): %d!", m.owner, gid))
    55.  }
    56.  // 调用次数减1
    57.  m.recursion--
    58.  if m.recursion != 0 { // 如果这个goroutine还没有完全释放,则直接返回
    59.   return
    60.  }
    61.  // 此goroutine最后一次调用,需要释放锁
    62.  atomic.StoreInt64(&m.owner, -1)
    63.  m.Mutex.Unlock()
    64. }
    65. func main() {
    66.  var mutex = &ReentrantMutex{}
    67.  mutex.Lock()
    68.  mutex.Lock()
    69.  fmt.Println(111)
    70.  mutex.Unlock()
    71.  mutex.Unlock()
    72. }

    最后给自己的原创 Go 面试小册打个广告,如果你从事 Go 相关开发,欢迎扫码购买,目前 10 元买断,加下面的微信发送支付截图额外赠送一份自己录制的 Go 面试题讲解视频

    7a596ecd941dfb193a4cab58a4fea290.jpeg

    aa87b1ed6b931ec6b3c1e9355c50062f.png

    如果对你有帮助,帮我点一下在看或转发,欢迎关注我的公众号

  • 相关阅读:
    精度和召回率的区别
    【鸿蒙OH-v5.0源码分析之 Linux Kernel 部分】003 - vmlinux.lds 链接脚本文件源码分析
    fluttter学习之ButtonStyle 、MaterialStateProperty
    代码随想录-014-剑指Offer707.设计链表
    linux mtd分区应用操作sample之某分区擦除
    a16z:推翻互联网的偶然君主制,如何设计Web3平台治理?
    附下载 | 图解密评联委会《商用密码应用安全性评估FAQ(第二版)》
    Github学生认证
    【K8S系列】kubadm部署k8s时service-cidr网络和pod-network-cidr的地址如何定义
    如何使用springboot+redis开发一个简洁的分布式锁?
  • 原文地址:https://blog.csdn.net/caspar_notes/article/details/133109893