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