为什么下面的ruby代码在GVL中显示了竞态条件?

hi3rlvi2  于 2023-05-17  发布在  Ruby
关注(0)|答案(1)|浏览(98)

我正在阅读许多关于GVL的文章,并试图了解它的功能。
我目前的理解是:

  • GVL是一种全局Mutex,只允许一个本地线程执行Ruby代码。
  • 只有持有GVL的线程才能执行Ruby代码
  • 然而,如果持有GVL的线程执行I/O操作,则该线程释放GVL,使得另一个线程获取GVL,从而导致并发运行。

因此,如果我编写的代码不包含任何I/O操作,我可以安全地假设不存在由于并发而导致的竞争条件。
在编写了以下代码之后,我注意到变量to在某种程度上超过了10000000(初始值from.)。

from = 100000000
to = 0

50.times.map do
  Thread.new do
    while from > 0
      from -= 1
      to += 1
    end
  end
end.map(&:join)

puts "from: #{from}"
puts "to: #{to}"

to总是通过在代码中插入Mutex来得到100000000,但为什么会这样呢?据我所知,由于GVL,while from > 0条件只由一个线程同时计算,不应该有这样的竞争条件。

我期待的结果是

from: 0
to: 10000000
u0sqgete

u0sqgete1#

GVL不仅在线程执行IO时被释放。
假设一个线程根本不做任何IO,那么如果GVL只在IO时释放,其他线程将永远不会有机会。
相反,一个线程完全有可能在任何原子操作之后都必须等待其他线程。例如,直接在检查while from > 0条件之后或在from -= 1to += 1之间。
值得注意的是,to += 1甚至不是一个原子操作。它是to = to + 1的快捷方式,可以在计算右侧和设置新值之间中断。
当你真正需要线程安全时,你需要实现互斥锁或信号量。

相关问题