15

array在 Ruby 中,如果被许多线程修改,则此代码不是线程安全的:

array = []
array << :foo # many threads can run this code

为什么<<操作不是线程安全的?

4

5 回答 5

10

实际上使用 MRI(Matz 的 Ruby 实现),GIL(全局解释器锁)使任何纯 C 函数原子化。

由于Array#<<在 MRI 中作为纯 C 代码实现,因此该操作将是原子的。但请注意,这仅适用于 MRI。在 JRuby 上,情况并非如此。

为了完全理解发生了什么,我建议你阅读这两篇文章,它们很好地解释了一切:

没有人了解 GIL
没有人了解 GIL - 第 2 部分

于 2013-07-20T23:10:57.463 回答
9

array是您应用类似操作时的程序变量<<。它分三个步骤发生:

  • 该变量首先被复制到 CPU 寄存器中。
  • CPU 执行计算。
  • CPU 将结果写回变量内存。

所以这个高级的单操作分三步执行。在这些步骤之间,由于线程上下文切换,其他线程可能会读取变量的相同(旧)值。这就是为什么它不是原子操作。

于 2013-07-20T18:10:16.420 回答
9

如果您有多个线程访问同一个数组,请使用 Ruby 的内置Queue类。它很好地处理了生产者和消费者。

这是文档中的示例:

require 'thread'

queue = Queue.new

producer = Thread.new do
  5.times do |i|
    sleep rand(i) # simulate expense
    queue << i
    puts "#{i} produced"
  end
end

consumer = Thread.new do
  5.times do |i|
    value = queue.pop
    sleep rand(i/2) # simulate expense
    puts "consumed #{value}"
  end
end

consumer.join
于 2013-07-20T19:38:23.673 回答
1

因为 Ruby 是一种非常高级的语言,所以在操作系统级别没有什么是真正原子的。只有非常简单的汇编操作在 OS 级别是原子的(OS 依赖),而每一个 Ruby 操作,即使是一个简单的操作也1 + 1对应着成百上千的汇编指令执行,例如方法查找、垃圾回收、对象初始化、范围计算等。

如果您需要使操作原子化,请使用互斥锁。

于 2013-07-20T19:01:29.510 回答
0

只是模仿@Linuxios 和@TheTinMan:高级语言(HLL)操作通常不是原子的。原子性(通常)在单线程程序中不是问题。在多线程程序中,您(程序员)必须以比单个 HLL 操作更高的粒度对其进行推理,因此具有原子的单个 HLL 操作实际上对您没有太大帮助。另一方面,尽管使 HLL 操作原子化前后只需要几条机器指令——至少在现代硬件上是这样——静态(二进制大小)和动态(执行时间)开销加起来。更糟糕的是,显式原子性几乎禁用了所有优化,因为编译器无法跨原子操作移动指令。没有真正的好处 + 巨大的成本 = 非首发。

于 2013-07-22T21:14:54.650 回答