You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

关于某段锁操作代码的死锁与竞态条件疑问

死锁判断与读写锁竞态条件解惑

嘿,先来说你第一个问题:你问“此方法中是否存在死锁”,但没附上具体的方法代码呀😅。死锁的本质是线程间循环等待对方持有的资源且都不肯释放,要判断的话,得看看方法里锁的获取顺序、线程之间的交互逻辑才行,你可以把代码贴出来,我帮你仔细分析~

接下来重点聊你疑惑的那个读写锁竞态条件问题:

死锁问题虽严重,但不如数据损坏恶劣。有一段代码先获取read lock、搜索列表,未找到目标后释放read lock,再获取write lock并插入对象,这段代码被指出存在race condition。我无法理解其中缘由,我的认知是必须在所有读锁都释放后才能获取写锁,而此场景下代码只会在无读锁时才会获取写锁,为何还会存在竞态条件?

这个坑其实出在读锁释放到写锁获取之间的“时间窗口”,咱们用两个线程的执行流程模拟一遍,你马上就懂了:

假设线程A和线程B都要往同一个列表里插入同一个不存在的对象X:

  • 线程A先拿到读锁,搜了一圈没找到X,然后释放读锁
  • 就在线程A准备拿写锁的空档,线程B也拿到了读锁,同样搜了一圈没找到X,也释放了读锁
  • 接着线程A顺利拿到写锁,把X插了进去
  • 线程B也紧跟着拿到写锁,又把X插了一次
  • 最后列表里出现了两个X,这就完全违反了“只有不存在才插入”的业务逻辑——这就是竞态条件导致的问题!

你说的“所有读锁释放后才能获取写锁”没错,读写锁的写锁确实是排他的,但问题在于**“读→释放→写”这一串操作不是原子的**。读锁释放后到写锁拿到手之前,其他线程完全可以钻空子完成同样的读操作,导致多个线程都判断“可以插入”,最终重复写入。

怎么解决呢?给你几个常见方案:

  • 方案一:全程持有写锁——简单粗暴,但会牺牲读性能,因为其他读线程都得等着
  • 方案二:用读锁升级(如果你的读写锁支持的话)——也就是拿着读锁的时候直接升级成写锁,这样中间就没有空档了;不过要注意,有些读写锁实现不支持升级,或者升级时得额外处理避免死锁的逻辑
  • 方案三:用原子性的“检查-插入”操作——比如很多并发容器自带的putIfAbsent这类方法,底层帮你把检查和插入做成了原子操作,不用自己手动拆成两次锁操作

核心逻辑就是:“检查是否存在”和“执行插入”必须是一个不可分割的原子操作,把它们拆成两个独立的锁阶段,就等于给竞态条件开了后门。


内容的提问来源于stack exchange,提问作者abjoshi - Reinstate Monica

火山引擎 最新活动