这不是Go的另一个BUG吗?

发布于:2024-10-24 编辑:匿名 来源:网络

大家好,大家好,我是小楼。最近又写了一个bug。

在线服务陷入僵局。幸运的是,这是一项新服务,没有造成太大影响。

问题出在Go的读写锁上。如果您正在编写Java,则不必将其划掉。

您还应该阅读这篇文章。本文的重点是Java和Go的读写锁的比较。

读完之后你甚至会有一种模糊的感觉。感想:Go的读写锁有bug吗?简单抽象一下故障重放的背景:服务端服务(Go语言实现)提供http接口,客户端服务调用该接口。

整体架构非常简单,不用画架构图也能看懂。这两个服务已经上线并运行了一段时间,没有出现任何问题。

突然有一天,客户端向服务器调用的所有接口都超时了。遇到这种问题,我第一时间查看日志和监控。

客户端全是超时日志,服务器端日志没有任何异常。连请求的监听也没有上报,就好像客户端请求没有到达服务器端一样。

于是我就去服务器手动请求接口,结果却是卡住了。现在客户被排除在外了。

服务器端肯定有问题。这种卡住的问题其实很容易排查。

直接用pprof看协程卡在哪里基本上就可以得出结论了(类似于Java的jstack的工具)。不过这个服务并没有开启pprof,所以只能改代码开启pprof。

重新发布并等待下次问题再次出现。幸运的是,我很幸运。

2天后问题就出来了。使用pprof查看程序卡在哪里:L1.png结果卡在一个地方,判断集群或者服务是否流量小。

该接口将接受集群名称或服务。名称的参数,然后判断集群或者服务是否是低流量集群,然后做一系列的事情。

做什么并不重要。小流量集群在配置中心配置。

我提取了这段代码(图为判断集群分支,下面的代码在更简单的服务分支中解释,底层是一样的)。为了避免漏洞,我简单解释一下程序逻辑:首先小流量的配置定义了一个读写锁(sync.RWMutex),在内存中维护了哪些业务需要灰度的规则(scopesMap) L2 .png 当配置发生变化时,调用reset刷新scopesMap,使用写锁,后续逻辑省略L3.png判断是否为灰度服务。

首先添加读锁,查看规则是否存在:L4.png,然后加锁判断服务是否符合规则:L5。 png这样把关键点圈出来,一看就知道问题所在。

读锁加了两次。第二次是不必要的,也是一个错误。

确实,删除第二个添加读锁的代码就能解决问题。如果事情到这里就结束了,那么这篇文章就没有必要写了。

我们来分析一下为什么会出现死锁。为什么会出现僵局?当我看到这个结果时,我的第一反应是Go的锁的重入问题。

熟悉Java的同学都熟悉锁的重入性。万一有些读者不明白锁的可重入性,我用一句话概括一下:可重入锁是一种可以重复进入的锁,也叫递归锁。

Java中有一个ReentrantLock。比如重复加锁就没有问题:L6.png。

但Go中的锁是不可重入的:L7.png。我也曾踩过这个坑。

这是Go的一个实现问题。只要你愿意,你也可以在Java中实现不可重入锁,但是Java中使用的大部分都是可重入锁,因为使用起来更加方便。

至于Go为什么不实现可重入锁,可以参考建宇刀《Go 为什么不支持可重入锁?》的这篇文章。原因可以归结为Go的设计者认为可重入锁是一个糟糕的设计,所以他们没有采用它。

不过我觉得这篇文章的评论更精彩:L8.png 说到这里,你可能会说,上面的问题显然是读写锁(sync.RWMutex)。读写锁有什么特点?阅读和阅读并不是相互排斥的。

阅读与写作、写作与写作是相互排斥的。由于读锁不是互斥的,即可以加两个读锁,那么读锁必须是可重入的。

我们写个demo来测试一下:L9.png确实是我们想的那样。我们看一下加读锁的逻辑: L10.png 看我框的代码。

如果有写锁等待,则读锁需要等待写锁! L11.png 这是什么逻辑?如果一个协程已经获得了读锁,而另一个协程尝试添加写锁,此时它应该无法添加。没有问题。

如果读锁协程再次尝试获取读锁,则需要等待写锁,这就是死锁!为了验证,我构建了一个demo:L12.png。该代码按照①、②、③的顺序执行。

②节的写锁需要等待①读锁释放,③节的读锁需要等待②节的写锁。释放最终是一个死锁逻辑。

仔细想想,这里最有争议的就是必须等待写锁之后才能再次进入读锁的逻辑。Java中也是这样吗?尝试编写一个演示:L13.pngJava 什么也没做,这是为什么?如果您有疑问,请查看源代码!不过Java的源码太长,不是本文的重点,所以我只讲几个重要的结论: Java的ReentrantReadWriteLock支持锁降级,但不能升级。

即已经获取写锁的线程可以继续获取读锁,但不能获取读锁。加锁线程无法再获取写锁; ReentrantReadWriteLock 实现了公平锁和非公平锁。

公平锁的情况下,在获取读锁或写锁之前,需要检查同步队列中的线程是否排在我之前;不公平锁情况:写锁可以直接抢占锁,但读锁获取有一个让步条件。如果当前同步队列head.next正在等待写锁,并且是不可重入的,那么它必须让步并等待。

在Java的实现下,如果一个线程持有读锁,那么写锁自然需要等待,但是持有读锁的线程也可以重新进入读锁。我们发现Java和Go的读写锁实现不一致。

这种不一致就是我们写下BUG的原因。这合理吗?抛开实现不谈,我们来思考一下。

这合理吗?协程(或线程)已获取读锁。当其他协程(线程)获取写锁时,必须等待读锁的释放。

既然这个协程(或线程)已经拥有读锁,为什么还要再次获取它呢?读锁时,是否需要检查是否有其他写锁在等待?你可以想象病人正在排队看医生。前面的病人向医生询问,进去后关上了门。

无论他在里面待多久(理论上),这都是他的权利。后面的病人要等他出来才能开门。

但Go的实现是,每次问完问题后,前一个病人都要看看门外,看看是否有人在等待。如果有人在等,那么他就得等门外的人问完之后再问,但是门外的人不问。

我们等着他问,大家就陷入了僵局,没有人愿意去看医生。仔细想一想,你认为这是Go的一个bug吗? Go为什么要这样实现?我尝试在github上搜索并发现这个问题:should nothang if thread has has a write-lock? #7 看有人怎么回答:L14.png 这家伙说,这不符合Go锁的原理。

Go 的锁不知道协程或线程信息。它只知道代码调用的顺序,即读写锁不能升级或降级。

Java中的锁记录了持有者(线程id),但是Go的锁不知道持有者是谁,所以获取读锁后,再次获取读锁。这里的逻辑是它无法区分持有者和其他人。

协程,所以统一处理。这实际上反映在Go源码的注释中。

后来才注意到:L15.png翻译为:如果一个协程持有读锁,另一个协程可能会调用Lock来添加写锁,那么就不再有协程可以获取读锁,直到前一个读锁被释放。这是为了禁用读锁递归。

它还确保锁最终可用;阻塞写锁调用将排除新的读锁。不过这个警告太不起眼了,大概有这样的效果: L16.png 这个场景很像产品和程序员: 产品经理:我要实现这个功能,我不在乎怎么实现 Go :这破坏了我的设计原则,不接受这个功能的产品经理:大家退一步,找个更便宜的方法来解决。

于是,程序员写了一篇关于读写锁的说明:L17.png 最后一个死锁坑,真的好容易踩。尤其是Java程序员写Go,所以我们在写Go代码的时候,还是要写得更像Go。

Go的设计者相当“偏执”,认为“糟糕”的设计不会被实现。例如,锁的实现不应该依赖于线程和协程信息;可重入(递归)锁是一个糟糕的设计。

所以这个看似有bug的设计也有一定的道理。当然,每个人都有自己的想法。

你觉得Go的读写锁这样实现合理吗?如果您读后觉得有所收获,请点赞并继续阅读。

这不是Go的另一个BUG吗?

站长声明

版权声明:本文内容由互联网用户自发贡献,本站不拥有所有权,不承担相关法律责任。如果发现本站有涉嫌抄袭的内容,欢迎发送邮件 举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。

标签:

相关文章

  • 首次发布 -阳光融汇资本完成新基金首关

    首次发布 -阳光融汇资本完成新基金首关

    据投资界1月17日消息,年初,阳光融汇资本宣布完成创新成长第二期首关基金。 基金总规模20亿元。 超出范围 70%。 至此,阳光融融资本总管理规模已突破亿元。 本基金将继续重点投资医疗健康及新兴技术领域,关注医疗行业优先临床价值、健康经济、产业创新升级的结构性机会,关

    06-18

  • 设立100亿元产业基金!山东临淄研发“核心”技术!

    设立100亿元产业基金!山东临淄研发“核心”技术!

    12月8日,首届中国(淄博)“核心材料、核心技术、核心动能”高峰论坛在山东临淄开幕。 高端封装测试项目、磁性功能材料、芯片及智能制造核心零部件生产等相关产业项目集中签约。 位于淄博市临淄区规划设立的临淄集成电路材料产业园是我国首个集成电路材料产业园。 园区主要从

    06-08

  • ChatGPT 的对话框已经过时了吗?这个AI产品提供了一种非常新的聊天方式

    ChatGPT 的对话框已经过时了吗?这个AI产品提供了一种非常新的聊天方式

    “消息ChatGPT”当我们打开一个话题时,这句话会默认写在ChatGPT的空输入框中。 与ChatGPT的交互就像和朋友聊天一样,你来我往,从上到下,线性结构非常直观。 然而,这是与人工智能交互的最佳方式吗?您还在用 ChatGPT 查找聊天记录吗?一种非常新的交互方式已经到来。 当我

    06-21

  • 存储变革:数据从核心到边缘,企业存储复兴

    存储变革:数据从核心到边缘,企业存储复兴

    每年创建、收集或复制的数据集合就是全球数据圈,到2020年将增长5倍以上。 IDC预测全球数据圈将从2018年的33ZB增长到2018年的ZB。 据雷锋网报道,2月21日,希捷科技“数字能源绽放——从边缘到核心——全球数据圈暨中国白皮书大会”正式召开。 本次会议由希捷科技主办,国际数

    06-18

  • 智能照明品牌Yeelight宜来完成C+轮融资

    智能照明品牌Yeelight宜来完成C+轮融资

    据投资界4月10日消息,据36氪消息,智能照明品牌Yeelight宜来近日完成C+轮融资,由中信金石和中信金石共同投资。 光大控股,并发布中文版,品牌名称为伊莱。 此前,Yeelight已获得多轮融资。 具体如下表所示: 本轮资金将主要用于照明新材料和新产品的研发、渠道和服务网络的

    06-18

  • WEFLY获得数千万美元天使轮投资,渶策资本、线性资本共同投资,

    WEFLY获得数千万美元天使轮投资,渶策资本、线性资本共同投资,

    投资圈(ID:pedaily)据5月5日消息,国内eVTOL(电动垂直起降飞行器)创新公司WEFLY宣布完成数千万美元投资美元天使轮融资,本轮本轮融资由渶策资本与线性资本共同投资,沧澜资本担任独家投资方。 本轮资金将主要用于产品研发以及核心技术团队的创建和扩充。 WEFLY是中国一家

    06-18

  • “运去哪”宣布完成1亿美元D1轮融资,加强海外物流网络建设

    “运去哪”宣布完成1亿美元D1轮融资,加强海外物流网络建设

    智慧物流在线服务平台“运去哪”正式宣布完成美国D1轮融资1亿美元。 本次融资完成后,云去哪儿成为国际物流数字化领域首家完成D轮融资的中国公司,估值达到独角兽级别(10亿美元)。 本轮融资完成后,云船纳将进一步加强海外网络建设以及数字新技术在国际物流中的应用。

    06-17

  • Xilinx 首席执行官概述公司新愿景和战略蓝图

    Xilinx 首席执行官概述公司新愿景和战略蓝图

    2020 年 3 月 19 日,加利福尼亚州圣何塞 — 全球自适应和智能计算领导者 Xilinx, Inc.(纳斯达克代码:XLNX)总裁兼首席执行官(首席执行官彭维克今天透露了公司的未来愿景和战略蓝图。 Peng的愿景是为Xilinx带来新发展、新技术和新方向,打造“自适应计算加速平台”。 在这

    06-06

  • 星宇科技参加2019高交会,发布北斗高精度可穿戴解决方案

    星宇科技参加2019高交会,发布北斗高精度可穿戴解决方案

    第21届深圳高交会于11月17日圆满落幕。 在为期5天的展会中,星宇科技带来了北斗高精度系列可穿戴设备。 展品上,解决方案亮相,全球首款基于SoC芯片支持北斗高精度定位的可穿戴解决方案发布,让北斗高精度在现场掀起一波热潮!星宇科技CEO吕韶清发布了高精度可穿戴解决方案,

    06-18

  • 高德发布好租赁3.0,巡更网融合促进可持续发展,

    高德发布好租赁3.0,巡更网融合促进可持续发展,

    从技术融合到业务业态融合,出租车巡网融合将迎来可持续发展的关键一年。 4月25日,出租车新旧业态融合发展研讨会在重庆召开。 来自全国百余家出租车企业和巡更网集成平台的代表参加了会议。 研讨会上,高德发布了“好租3.0”巡网一体化解决方案,通过供需匹配的价格调节能力

    06-17

  • 获取 Apple Vision Pro!这东西卖2万多元,真的适合吗?

    获取 Apple Vision Pro!这东西卖2万多元,真的适合吗?

    亲爱的乡亲们朋友们,今天对于数字爱好者来说是非常热闹的一天。 各种自媒体可以说是从早忙到晚,不为别的,就是国子哥新品Vision Pro正式发货。 苹果在去年的 WWDC 开发者大会上宣布的这款全新设备看起来像是一款虚拟现实/混合现实头戴式显示设备。 但果子正式称其为“空间计

    06-17

  • 积家宠物完成新一轮融资4000万元,宾盛金瑞基金投资

    积家宠物完成新一轮融资4000万元,宾盛金瑞基金投资

    投资圈(微信ID:pedaily)7月14日报道,积家宠物完成新一轮融资1万元,宾盛资本管理由国盛福瑞盛金瑞基金投资。 资吉嘉宠物集团(JIA PETS)是一家集宠物食品研发、制造于一体的综合性宠物产业集团。 通过近年来的快速发展,已成为全国排名前5的宠物企业之一,也是目前江苏省

    06-17