Подтвердить что ты не робот

Безопасно ли вилка из нити?

Позвольте мне объяснить: я уже разрабатываю приложение на Linux, которое создает и запускает внешний двоичный файл и ждет его завершения. Результаты сообщаются файлами shm, которые являются уникальными для процесса fork+. Весь код инкапсулируется внутри класса.

Теперь я рассматриваю процесс нарезки процесса, чтобы ускорить процесс. Имея много разных экземпляров функций класса fork и одновременно исполняя двоичный файл (с разными параметрами) и передавая результаты своим собственным уникальным файлам shm.

Является ли этот поток безопасным? Если я вилка в потоке, кроме безопасного, есть ли что-то, что я должен наблюдать? Любые советы или помощь очень ценятся!

4b9b3361

Ответ 1

fork ing, даже с потоками, безопасен. После того, как вы используете fork, потоки независимы для каждого процесса. (То есть нарезание резьбы ортогонально разветвлению). Однако, если потоки в разных процессах используют одну и ту же разделяемую память для общения, вам необходимо разработать механизм синхронизации.

Ответ 2

Проблема заключается в том, что fork() копирует только вызывающий поток, и любые мьютексы, содержащиеся в дочерних потоках, будут навсегда заблокированы в разветвленном дочернем элементе. Решение pthread было обработчиком pthread_atfork(). Идея заключалась в том, что вы можете зарегистрировать 3 обработчика: одну преформу, один родительский обработчик и один обработчик. Когда fork() происходит, prefork вызывается перед fork и, как ожидается, получит все мьютексы приложения. И родительский, и дочерний должны освобождать все мьютексы в родительском и дочернем процессах соответственно.

Это еще не конец истории! Библиотеки вызывают pthread_atfork для регистрации обработчиков для конкретных мьютексов библиотеки, например Libc делает это. Это хорошо: приложение не может знать о мьютексах, хранящихся в сторонних библиотеках, поэтому каждая библиотека должна вызывать pthread_atfork, чтобы убедиться, что собственные мьютексы очищены в случае fork().

Проблема заключается в том, что порядок, в котором обработчики pthread_atfork вызываются для несвязанных библиотек, это undefined (это зависит от порядка загрузки библиотек программой). Таким образом, это означает, что технически взаимоблокировка может произойти внутри обработчика предпродажа из-за состояния гонки.

Например, рассмотрим следующую последовательность:

  • Тема T1 вызывает fork()
  • обработчики prefork для libc, полученные в T1
  • Далее, в Thread T2, сторонняя библиотека A приобретает свой собственный мьютекс AM, а затем делает вызов libc, для которого требуется мьютекс. Это блокирует, потому что мьютексы libc удерживаются T1.
  • В потоке T1 выполняется предварительный обработчик для библиотеки A, которая блокирует ожидание получения AM, которое удерживается T2.

Там ваш тупик и его не связаны с вашими собственными мьютексами или кодом.

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

Ответ 3

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

Ответ 4

Хотя вы можете использовать поддержку Linux NPTL pthreads(7) для вашей программы, потоки неудобно подходят для систем Unix, как вы обнаружили с вашим вопросом fork(2).

Так как fork(2) - очень дешевая операция на современных системах, вы можете сделать это просто fork(2), когда у вас будет больше обработки. Это зависит от того, сколько данных вы намерены перемещать вперед и назад, философия "ничего не работает" для fork ed процессов хороша для уменьшения ошибок в общих данных, но означает, что вы должны создавать каналы для перемещения данных между процессами или использовать разделяемую память (shmget(2) или shm_open(3)).

Но если вы решите использовать потоки, вы можете fork(2) создать новый процесс со следующими подсказками из fork(2) manpage:

   *  The child process is created with a single thread — the
      one that called fork().  The entire virtual address space
      of the parent is replicated in the child, including the
      states of mutexes, condition variables, and other pthreads
      objects; the use of pthread_atfork(3) may be helpful for
      dealing with problems that this can cause.

Ответ 5

В "Рассвете времени" мы назвали потоки "легкими процессами", потому что, хотя они действуют как процессы, они не идентичны. Самое большое различие заключается в том, что потоки по определению живут в одном и том же адресном пространстве одного процесса. У этого есть преимущества: быстро переключение из потока в поток, они по сути делятся памятью, поэтому межпоточные коммуникации бывают быстрыми, а создание и удаление потоков выполняется быстро.

Здесь различают "тяжеловесные процессы", которые являются полными адресными пространствами. Новый тяжеловесный процесс создается вилкой (2). Поскольку виртуальная память попала в мир UNIX, это было дополнено vfork (2) и некоторыми другими.

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

В принципе, ваш ответ похож на это объяснение: когда у вас есть процесс со многими темами LWPs, и вы разблокируете процесс, у вас будет два независимых процесса со многими потоками, работающие одновременно.

Этот трюк даже полезен: во многих программах у вас есть родительский процесс, который может иметь много потоков, некоторые из которых развивают новые дочерние процессы. (Например, HTTP-сервер может это сделать: каждое соединение с портом 80 обрабатывается потоком, а затем дочерний процесс для чего-то вроде программы CGI может быть разветвлен, тогда exec (2) будет вызван для запуска программы CGI вместо закрытия родительского процесса.)

Ответ 6

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

Пока каждый процесс использует разные файлы, не должно быть никаких проблем.

Ответ 7

Если вы быстро или вызываете exec или _exit в разветвленном дочернем процессе, вы в порядке на практике.

Вместо этого вы можете использовать posix_spawn(), который, вероятно, сделает правую вещь.