Почему запись одновременно в файл с использованием разных процессов приводит к странным результатам? - PullRequest
1 голос
/ 03 мая 2020

Я пытаюсь понять процессы с Ruby. Я создаю 4 дочерних процесса из моего родительского процесса. Основной процесс начинается с записи в файл, затем создаются дочерние процессы, каждый из которых выполняет запись в один и тот же файл:

require 'csv'
a = [1, 2, 3, 4]
CSV.open("temp_and_cases_batch_parallel.csv", "ab") do |target_file|
  target_file << ["hello from parent process #{Process.pid}"]
  a.each do |num|
    pid = Process.fork do
      target_file << ["hello from child Process #{Process.pid}"]
    end
    puts "parent, pid #{Process.pid}, waiting on child pid #{pid}"
  end
end
Process.wait
puts "parent exiting"

Ожидается вывод файла

hello from parent process 3336
hello from child Process 3350
hello from child Process 3351
hello from child Process 3349
hello from child Process 3352

Файл вывод, который я на самом деле получаю:

hello from parent process 3336
hello from parent process 3336
hello from child Process 3350
hello from parent process 3336
hello from child Process 3351
hello from parent process 3336
hello from child Process 3349
hello from parent process 3336
hello from child Process 3352

похоже, что вставка из родительского процесса повторяется 5 раз. Как это возможно? что здесь происходит?

1 Ответ

4 голосов
/ 03 мая 2020

Наличие нескольких процессов, записывающих в один и тот же файл, обычно не очень хорошая идея. В большинстве случаев, если вы точно не знаете, что делаете, результат будет непредсказуемым, как вы только что продемонстрировали на своем примере.

Причина, по которой вы получаете странный результат, заключается в том, что объект Ruby IO имеет собственный внутренний буфер . Этот буфер хранится в памяти и НЕ гарантированно записывается на диск при вызове <<.

. Здесь происходит то, что строка hello from parent записывается только во внутренний буфер, а не в диск. Затем, когда вы позвоните fork, вы будете копировать этот буфер в дочерний. Затем дочерний элемент добавит hello from child в буфер, и только тогда буфер будет записан на диск.

В результате все дочерние элементы будут записывать hello from parent, в дополнение к записи hello from child, поскольку это то, что будет содержать внутренний буфер памяти к тому времени, когда Ruby решит записать буфер на диск.

Чтобы обойти эту проблему, вы можете вызвать IO.flush перед разветвлением, чтобы убедиться, что буфер памяти пуст и сбрасывается на диск перед разветвлением. Это гарантирует, что буфер у дочернего элемента пуст, и теперь вы получите ожидаемый результат:

CSV.open(...) do |target_file|
  target_file << ...
  target_file.flush  # <-- Make sure the internal buffer is flushed to disk before forking

  a.each do |num|
    ... Process.fork ...
  end
end
...
...