Проблема в том, что fork () копирует только вызывающий поток, и любые мьютексы, содержащиеся в дочерних потоках, будут навсегда заблокированы в разветвленном дочернем элементе.Решением pthread были pthread_atfork()
обработчики.Идея заключалась в том, что вы можете зарегистрировать 3 обработчика: один prefork, один родительский обработчик и один дочерний обработчик.Когда fork()
происходит, prefork вызывается перед fork и, как ожидается, получит все мьютексы приложения.И родитель, и ребенок должны освободить все мьютексы в родительском и дочернем процессах соответственно.
Это еще не конец истории!Библиотеки вызывают pthread_atfork
, чтобы зарегистрировать обработчики для специфичных для библиотеки мьютексов, например, Libc делает это.Это хорошо: приложение не может знать о мьютексах, хранящихся в сторонних библиотеках, поэтому каждая библиотека должна вызывать pthread_atfork
, чтобы убедиться, что ее собственные мьютексы очищены в случае fork()
.
Проблема в том, что порядок вызова обработчиков pthread_atfork
для несвязанных библиотек не определен (это зависит от порядка загрузки библиотек программой).Таким образом, это означает, что технически тупик может возникнуть внутри обработчика префорков из-за состояния гонки.
Например, рассмотрим следующую последовательность:
- Поток T1 вызывает
fork()
- Обработчики преформ libc вызываются в T1 (например, T1 теперь содержит все блокировки libc)
- Затем в потоке T2 сторонняя библиотека A получает собственный мьютекс AM, а затем выполняет вызов libc, для которого требуется мьютекс.Это блокирует, потому что мьютексы libc хранятся в T1.
- Поток T1 запускает обработчик prefork для библиотеки A, которая блокирует ожидание получения AM, который удерживается T2.
тупик и не связанный с вашими мьютексами или кодом.
Это действительно произошло в проекте, над которым я когда-то работал.Совет, который я нашел в то время, состоял в том, чтобы выбрать вилку или нити, но не оба.Но для некоторых приложений это, вероятно, не практично.