Почему операция << на массиве в Ruby не является атомарной?

В Ruby этот код не является потоковым, если array изменен многими потоками:

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

Почему операция << не безопасна для потоков?

Ответ 1

array - это ваша программная переменная, когда вы применяете к ней операцию типа <<. Это происходит в три этапа:

  • Переменная сначала копируется в регистр CPU.
  • Процессор выполняет вычисления.
  • Процессор записывает результат в переменную память.

Таким образом, эта одноуровневая операция высокого уровня выполняется в три этапа. В промежутке между этими шагами, из-за переключения потоков, другой поток может читать одно и то же (старое) значение переменной. Вот почему это не атомная операция.

Ответ 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

Ответ 3

Фактически, используя MRI (реализация Matz Ruby), GIL (Global Interpreter Lock) делает любую чистую C-функцию атомной.

Так как Array#<< реализован как чистый C-код в MRI, эта операция будет атомарной. Но обратите внимание, что это относится только к МРТ. На JRuby это не так.

Чтобы полностью понять, что происходит, я предлагаю вам прочитать эти две статьи, которые объясняют все очень хорошо:

Никто не понимает GIL
Никто не понимает GIL - часть 2

Ответ 5

Поскольку Ruby является языком с очень высоким уровнем, на уровне ОС на самом деле ничего не происходит. Только очень простые операции сборки являются атомарными на уровне ОС (зависит от ОС), и каждая операция Ruby даже простая 1 + 1 соответствует сотням или тысячам инструкций по сборке, таких как поиск методов, сбор мусора, инициализация объектов, вычисление областей и т.д.

Если вам нужно сделать операции атомарными, используйте мьютексы.

Ответ 6

Простое отключение @Linuxios и @TheTinMan: операции высокого уровня (HLL) вообще не являются атомарными. Атомность (как правило) не проблема в однопоточных программах. В многопоточных программах вы (программист) должны рассуждать об этом с гораздо большей детализацией, чем одна операция HLL, поэтому отдельные операции HLL, которые являются атомарными, на самом деле вам не помогают. С другой стороны, хотя при выполнении операции HLL атома занимает всего несколько машинных команд до и после, по крайней мере, на современном оборудовании, статические (двоичный размер) и динамические (время выполнения) накладные расходы складываются. Хуже того, явная атомарность в значительной степени отключает всю оптимизацию, поскольку компиляторы не могут перемещать инструкции по атомным операциям. Нет реальной выгоды + значительная стоимость = не стартер.