Как используется операция 'fork'?ruby-74

Операция fork — это системный вызов Unix, который создает новый процесс путем копирования текущего. В Ruby он предоставляет мощные возможности для параллельной обработки.

Основы работы fork

1. Простейший пример

pid = fork do
  # Этот код выполняется в дочернем процессе
  puts "Child process: #{Process.pid}"
end

puts "Parent process: #{Process.pid}"
Process.wait(pid) # Ожидание завершения дочернего процесса

2. Что происходит при fork?

  • Создается точная копия родительского процесса
  • Копируется вся память (технически используется copy-on-write)
  • В Ruby блок передается только дочернему процессу

Ключевые сценарии использования

1. Параллельная обработка данных

data_chunks = [chunk1, chunk2, chunk3]

data_chunks.each do |chunk|
  fork do
    process_chunk(chunk) # Обработка в отдельном процессе
  end
end

Process.waitall # Ожидание всех дочерних процессов

2. Изоляция опасных операций

def safely
  pid = fork do
    begin
      yield
    rescue => e
      exit!(1) # Выход с кодом ошибки
    end
  end

  _, status = Process.wait2(pid)
  status.success?
end

safely { dangerous_operation() }

3. Создание демонов

pid = fork do
  Process.setsid # Стать лидером новой сессии
  exit if fork   # Создать "внутреннего" демона

  # Код демона
  loop do
    perform_task
    sleep 60
  end
end

Process.detach(pid) # Отсоединить демона

Работа с ресурсами

1. Разделение файловых дескрипторов

reader, writer = IO.pipe

fork do
  reader.close
  writer.write "Message from child"
end

writer.close
puts reader.read # => "Message from child"

2. Копирование памяти

large_array = Array.new(1_000_000) { "data" }

fork do
  # До модификации память разделяется
  large_array[0] = "modified" # Теперь копируется страница памяти
end

Управление процессами

1. Ожидание завершения

pids = []
3.times do |i|
  pids << fork { sleep i; puts "Done #{i}" }
end

pids.each { |pid| Process.wait(pid) }

2. Перехват сигналов

trap('TERM') { puts "Child terminated"; exit }

fork do
  # Код, реагирующий на сигналы
  sleep 10
end

Process.kill('TERM', pid)

Ограничения и проблемы

  1. Передача объектов:

    • Нельзя передать уже созданные объекты между процессами
    • Решение: использовать Marshal или JSON
  2. Глобальные состояния:

    • Соединения к БД, сокеты и т.д. не сохраняются
    • Нужно переподключаться в дочернем процессе
  3. Отладка:

    • Сложнее отлаживать параллельные процессы
    • Решение: логгирование в файлы

Альтернативы fork

  1. Threads:

    • Легче, но есть GIL в MRI Ruby
    • Общая память, но риск состояний гонки
  2. Process.spawn:

    • Легче для запуска внешних команд
    • Нет копирования всей памяти процесса
  3. Distributed workers:

    • Sidekiq, Resque для фоновых задач
    • Лучше для масштабирования

Безопасность

  1. Ограничение ресурсов:
fork do
  Process.setrlimit(:CPU, 10) # Ограничить CPU 10 секундами
  infinite_loop()
end
  1. Изоляция:
fork do
  Dir.chdir('/tmp') # Изменить рабочий каталог
  # Дальнейшая изоляция
end

Резюмируем

Операция fork в Ruby позволяет:

  • Создавать полностью изолированные процессы
  • Параллельно обрабатывать данные
  • Изолировать опасные операции
  • Создавать демонизированные процессы

Основные правила:

  1. Используйте fork для CPU-интенсивных задач
  2. Помните о разделении ресурсов
  3. Всегда ожидайте завершения дочерних процессов
  4. Для IO-операций рассмотрите потоки
  5. Для фоновых задач - специализированные системы

Пример выбора:

  • Вычисления → fork
  • Фоновые задачи → Sidekiq
  • Параллельный IO → Threads
  • Внешние команды → spawn