array
在 Ruby 中,如果被许多线程修改,则此代码不是线程安全的:
array = []
array << :foo # many threads can run this code
为什么<<
操作不是线程安全的?
array
在 Ruby 中,如果被许多线程修改,则此代码不是线程安全的:
array = []
array << :foo # many threads can run this code
为什么<<
操作不是线程安全的?
实际上使用 MRI(Matz 的 Ruby 实现),GIL(全局解释器锁)使任何纯 C 函数原子化。
由于Array#<<
在 MRI 中作为纯 C 代码实现,因此该操作将是原子的。但请注意,这仅适用于 MRI。在 JRuby 上,情况并非如此。
为了完全理解发生了什么,我建议你阅读这两篇文章,它们很好地解释了一切:
array
是您应用类似操作时的程序变量<<
。它分三个步骤发生:
所以这个高级的单操作分三步执行。在这些步骤之间,由于线程上下文切换,其他线程可能会读取变量的相同(旧)值。这就是为什么它不是原子操作。
如果您有多个线程访问同一个数组,请使用 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
因为 Ruby 是一种非常高级的语言,所以在操作系统级别没有什么是真正原子的。只有非常简单的汇编操作在 OS 级别是原子的(OS 依赖),而每一个 Ruby 操作,即使是一个简单的操作也1 + 1
对应着成百上千的汇编指令执行,例如方法查找、垃圾回收、对象初始化、范围计算等。
如果您需要使操作原子化,请使用互斥锁。
只是模仿@Linuxios 和@TheTinMan:高级语言(HLL)操作通常不是原子的。原子性(通常)在单线程程序中不是问题。在多线程程序中,您(程序员)必须以比单个 HLL 操作更高的粒度对其进行推理,因此具有原子的单个 HLL 操作实际上对您没有太大帮助。另一方面,尽管使 HLL 操作原子化前后只需要几条机器指令——至少在现代硬件上是这样——静态(二进制大小)和动态(执行时间)开销加起来。更糟糕的是,显式原子性几乎禁用了所有优化,因为编译器无法跨原子操作移动指令。没有真正的好处 + 巨大的成本 = 非首发。