Процесс порождения из многопоточного приложения - PullRequest
11 голосов
/ 16 ноября 2011

У меня есть ситуация, когда мне нужно порождать вспомогательный процесс из очень большого многопоточного приложения, над которым у меня нет полного контроля.

Сейчас я использую fork() / exec().Это работает большую часть времени, но в некоторых случаях ребенок странно падает до того, как произойдет exec().Я подозреваю, что это потому, что fork() многопоточные приложения, как правило, считаются действительно плохой идеей.

Я бы очень, очень хотел бы способ запустить процесс атомарно, без fork() родительского процесса: при закрытых дескрипторах файлов, настройке среды так, как я хочу, установке CWD и т. д. Это позволит избежать всех ужасов fork() использования моего многопоточного родительского приложения, работы с наследованием дескрипторов файлов и т. д.идеально.К сожалению, в Linux posix_spawn() реализован с использованием fork(), а exec() ...

vfork() определено для приостановки родительского процесса до тех пор, пока дочерний процесс не вызовет exec().Казалось бы, это больше похоже на то, что я хочу, но, насколько я понимаю, vfork() обычно считается исторической реликвией в наши дни и эквивалентно fork() --- это все еще так?

Что такоенаименее плохой способ справиться с этим?

Заметьте, что:

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

Это на Linux.Java задействована, но весь мой код находится в C.

Ответы [ 2 ]

5 голосов
/ 16 ноября 2011
Многопоточное приложение

fork'ing считается безопасным, если вы используете только асинхронно-безопасные операции. POSIX говорит :

Процесс должен быть создан с одним потоком. Если многопоточный процесс вызывает fork (), новый процесс должен содержать реплику вызывающего потока и всего его адресного пространства, возможно, включая состояния мьютексов и других ресурсов. Следовательно, чтобы избежать ошибок, дочерний процесс может выполнять только асинхронно-безопасные операции до тех пор, пока не будет вызвана одна из функций exec. Обработчики вил могут быть установлены с помощью функции pthread_atfork (), чтобы поддерживать инварианты приложений в вызовах fork ().

posix_spawn () - не лучшая идея:

Также сложно временно изменить среду многопоточного процесса, поскольку все потоки должны договориться, когда это безопасно для изменения среды. Однако эту стоимость несут только те вызовы posix_spawn () и posix_spawnp (), которые используют дополнительную функциональность. Поскольку обширные модификации не являются обычным случаем и особенно маловероятны в критичном по времени коде, сохранение большей части контроля над средой вне posix_spawn () и posix_spawnp () является подходящим дизайном.

(см. man posix_spawn )

Полагаю, у вас проблемы с копированием из родительских ресурсов. Вы можете очистить их с помощью обработчика pthread_atfork () (вы используете pthread, верно?). Другой способ - использовать низкоуровневую функцию для создания процесса, которая называется clone () . Это дает вам почти полный контроль над тем, что именно дочерний процесс должен наследовать от своего родителя.

[ОБНОВЛЕНО]

Вероятно, самый простой способ избавиться от проблемы - это изменить схему форка. Например, вы можете создать новый процесс (форк) еще до того, как ваша программа инициализирует все ресурсы. То есть вызовите fork () в main (), прежде чем создавать все свои потоки. В дочернем процессе установите обработчик сигнала (например, для сигнала SIGUSR2) и режим сна. Когда родитель должен выполнить какой-то новый процесс, он отправляет сигнал SIGUSR2 вашему дочернему процессу. Когда ребенок ловит его, он вызывает fork / exec.

2 голосов
/ 16 ноября 2011

Вызов fork должен быть безопасным , если , вы ограничиваете себя «необработанными» системными вызовами (syscall(SYS_fork), syscalll(SYS_execve, ...) и т. Д.).Вызовите любую подпрограмму glibc, и у вас будет много неприятностей.

Вызов vfork - это вовсе , что вы хотите: только поток, вызвавший vforkприостановлено, и другие потоки будут продолжать работать (и в том же адресном пространстве, что и дочерний элемент vforked).Это очень может осложнить вашу жизнь.

Позвонить clone напрямую возможно, но чрезвычайно сложно.У нас есть реализация, которая позволяет безопасно разветвлять дочерние процессы из многопоточных приложений (к сожалению, не с открытым исходным кодом).Этот код очень хитрый и удивительно длинный.

...