Отличное описание вопроса! У тебя есть мой голос.
Теперь за ответ:
- До macOS 10.13 среда выполнения target-C не поддерживала использование между
fork()
и exec()
в дочернем процессе многопоточного родительского процесса. Вы можете просто не вызывать какой-либо метод target-C в этом интервале. Это приводит к состоянию гонки. то есть большую часть времени это будет работать, а иногда и не получится. Например: если бы поток в родительском процессе удерживал одну из блокировок среды выполнения Object-C, когда произошло fork()
, дочерний процесс 'заблокировался бы, когда он попытался бы взять эту блокировку.
- Начиная с macOS 10.13, среда выполнения Objective C теперь поддерживает использование "между"
fork()
и exec()
. Однако существуют ограничения, связанные с методами +initialize
. (Вы проблема в этой зоне).
Теперь, прежде чем предлагать решение. Позвольте мне пролить свет на сложность, связанную с fork
:
fork
создает копию процесса.
- Дочерний процесс заменяет себя другой программой, используя системный вызов
execve()
Пока все вроде нормально, верно? Дочерний процесс (worker
в вашем случае) имеет копию родительского процесса, и эта копия предоставляется дочернему процессу fork()
. Но, fork()
не копирует все! В частности, он не копирует потоки. Все потоки, запущенные в родительском процессе, не существуют в дочернем процессе
На этой заметке, сосредоточившись на вашей проблеме:
Хотя macOS 10.13+ поддерживает выполнение «чего угодно» между fork
и exec
. Тем не менее, очень неправильно делать что-либо между fork
и exec
. В вашем случае вызов q.put()
до p.start()
, как справедливо упомянуто @Darkonaut, запускает фидерный поток при первом вызове, и разветвление уже многопоточного приложения проблематично.
Это связано с тем, что методы +initialize
по-прежнему имеют ограничения вокруг fork()
. Проблема заключается в том, что гарантии безопасности потока +initialize
неявно вводят блокировки вокруг состояния, которое не контролируется средой выполнения Objective-C.
Когда вы вызываете q.put()
или используете библиотеку requests
(вызов в библиотеку популярных запросов, это в конечном итоге вызовет модуль _scproxy для получения системных прокси, и это в конечном итоге вызовет метод + initialize) перед тем, как p.start()
, любой из них приводит ваш родительский процесс к получению блокировки. Вы должны принять к сведению, что fork
создает копию процесса. В вашем случае, когда q.put()
вызывается до p.start()
, fork
происходит в неподходящее время, и вы workers
получаете копию родительского процесса, lock
в скопированном состоянии.
В вас worker
, вы делаете q.get()
. Это означает получение блокировки, но блокировка уже получена во время fork
(от родителя).
Дочерний процесс (worker
) ожидает освобождения lock
, но lock
никогда не будет выпущен. Потому что поток, который его освободит, не был скопирован fork()
.
Нет хорошего способа сделать +initialize
поточно-ориентированным и вилко-безопасным. Вместо этого среда выполнения Objective C просто останавливает процесс вместо выполнения какого-либо переопределения +initialize
в дочернем процессе:
+[SomeClass initialize] may have been in progress in another thread when fork() was called. We cannot safely call it or ignore it in the fork() child process. Crashing instead.
Надеюсь, что ответ на ваш вопрос 1.
Теперь по вопросу 2:
Несколько обходных путей от лучших к худшим:
- Ничего не делать между
fork()
и exec()
(лучше не использовать запросы между fork()
и exec*()
).
- Используйте только безопасные асинхронные операции между fork () и exec (). Список доступных функций здесь
- Определите переменную среды OBJC_DISABLE_INITIALIZE_FORK_SAFETY = YES, или добавьте раздел __DATA, __ objc_fork_ok, или создайте с использованием SDK, более раннего, чем macOS 10.13. Затем скрестите пальцы.