为什么golang不支持可重入锁

java中的可重入锁

熟悉java的朋友都知道,在java中不管是synchronized还是ReentrantLock都是可重入锁,使用起来也非常简单

synchronized

  • 同步实例方法
java 复制代码
public synchronized void instanceMethod() {
    // 同步方法的代码
}
  • 同步静态方法
java 复制代码
public static synchronized void staticMethod() {
    // 同步方法的代码
}
  • 同步代码块
java 复制代码
public void someMethod() {
    synchronized(this) {
        // 同步代码块
    }
}

上面都是基于this对象实例的锁,静态方法的锁是当前类的Class对象

当然也可以使用其他对象实例的锁,手动传入一个对象

java 复制代码
private final Object lock = new Object();

public void someMethod() {
    synchronized(lock) {
        // 同步代码块
    }
}

ReentrantLock

ReentrantLock就是手动挡,但是也支持重入

java 复制代码
public class XiaoZou {
    
    private final Lock lock = new ReentrantLock();

    public void someMethod() {
        lock.lock();
        lock.lock();
        try {
            // 同步代码块
        } finally {
            lock.unlock();
            lock.unlock();
        }
    }
}

核心原理

其实可重入锁的核心原理就是维护一个monitor计数器即可,每次加锁+1,解锁-1,当monitor0的时候才能释放锁

大致流程如下图

对于大佬们来说设计支持可重入锁的锁也是非常简单的,那为什么golang不支持呢?

golang中的Mutex

实际golang中比较简单经典的锁用法就是

go 复制代码
func main() {
	var mu sync.Mutex
	mu.Lock()
	defer mu.Unlock()
}

如果我们试试一把锁加多次会怎么样呢?

go 复制代码
func main() {
	var mu sync.Mutex
	mu.Lock()
	mu.Lock()
	defer mu.Unlock()
}

没错,报错了

复制代码
fatal error: all goroutines are asleep - deadlock!

说明golang是不支持可重入锁的

golang为什么不支持可重入锁

我们去golang官方社区,查找相关issues就会发现早有人提出一样的问题

go/issues/24192: https://github.com/golang/go/issues/24192

有一位小伙伴提出可重入锁通常意味着坏代码,然后贴上了stackoverflow的讨论地址
stackoverflow:https://stackoverflow.com/questions/14670979/recursive-locking-in-go/14671462#14671462

golang的官方说法,如果你遇到了需要使用重入锁的场景,实际是你的代码出现了坏味道,你需要重新设计你的代码

互斥锁的本意就是"我需要保持不变".可重入锁会破坏他们

我们看看官方给的例子

go 复制代码
     func F() {
             mu.Lock()
             ... do some stuff ...
             G()
             ... do some more stuff ...
             mu.Unlock()
     }

     func G() {
             mu.Lock()
             ... do some stuff ...
             mu.Unlock()
     }

通常,当调用mu.Lock返回时,调用代码现在可以假设受保护的不变量保持不变,直到它调用mu.Unlock

如果递归互斥锁的实现允许在当前线程已经持有 mu 时, 让 G 中的 mu.Lockmu.Unlock 调用变成无操作,

那么当在 G 内部 mu.Lock 返回时,不变量可能成立,也可能不成立 。这取决于 F 在调用 G 之前做了什么。

或许 F 并没有意识到 G 需要这些不变量,并且已经破坏了它们(在复杂代码中这完全有可能)

从互斥宏观角度来看,要么是F执行,要么是G执行,两者是互斥的,这就是mu的原语

但是可重入打破了这个原语,因为F和G都可以同时执行,这就是mu的原语被破坏了

所以如果你要实现类似的功能推荐你使用如下方式去代替

go 复制代码
     func g() {
             ... do some stuff ...
     }

     func G() {
             mu.Lock()
             g()
             mu.Unlock()
     }

递归互斥锁只是一个错误,不过是滋生漏洞的温床

总结

总的来说就是goalng的设计者Russ Cox认为可重入是一种错误设计,会破坏mu互斥的原语

本来在原语上两个应该互斥的方法因为可重入变的不互斥了,出现需要可重入锁的场景,实际上是你的代码出现了坏味道,需要重新设计

可以看到golang从设计层面就避免了很多坏代码的味道,比如不支持可重入锁,不支持循环引用等。从编译层面去规范代码,这也是golang的一大特色

但是也就带来了一些开发上的不便利,你需要花费更多的重新设计你的代码,但是这也是golang的设计初衷

质量和效率总归互斥的,你需要在两者之间做出平衡

相关推荐
Chester_19992 小时前
CSP202203C.计算资源调度器
开发语言·数据结构·c++·蓝桥杯
2601_962055973 小时前
跟据spring boot版本,查看对应的tomcat,并查看可支持的tomcat的版本范围
spring boot·后端·tomcat
EXI-小洲4 小时前
Java 操作 Word:字符串替换、图片插入、动态生成表格与API接口下载
java·开发语言·spring boot·word
会周易的程序员4 小时前
给 PLC 写一个字节码虚拟机:STVM 虚拟机架构设计
开发语言·c++·虚拟机·软plc·iec61131·stvm
机器视觉知识推荐、就业指导4 小时前
为什么老说“纯 Qt 没啥就业市场”?
开发语言·qt
telepan4 小时前
Qt 开发避坑与性能指南:如何优雅、安全地定义全局常量字符串?
开发语言·qt
m0_695251494 小时前
Qt MaintenanceTool 使用国内镜像加速下载(超详细教程)
开发语言·qt
Lost of 程序猿5 小时前
ASP.NET Core API 幂等性设计深度实战:从一次重复申领事故说起
后端·asp.net
大衛說5 小时前
《Python 从入门到精通》系列总览与学习路线
开发语言·python·学习
QQ_21696290966 小时前
【源码编号:project79475】SpringBoot校内二手交易平台:商品发布、分类检索、留言交流、订单管理全流程实战
java·spring boot·后端