У нас есть общая библиотека C ++, которая использует ZeroC's Ice для RPC, и если мы не выключим среду выполнения Ice, мы увидим, как дочерние процессы зависают на случайных мьютексах. Среда выполнения Ice запускает потоки, имеет много внутренних мьютексов и хранит открытые дескрипторы файлов на серверах.
Кроме того, у нас есть несколько собственных мьютексов для защиты нашего внутреннего состояния.
Наша общая библиотека используется сотнями внутренних приложений, поэтому мы не можем контролировать, когда процесс вызывает fork (), поэтому нам нужен способ безопасного отключения Ice и блокировки мьютексов, пока процесс разветвляется.
Чтение стандарта POSIX в pthread_atfork () при обработке мьютексов и внутреннего состояния:
В качестве альтернативы, некоторые библиотеки могли бы предоставить только дочернюю процедуру, которая повторно инициализирует мьютексы в библиотеке и всех связанных состояниях до некоторого известного значения (например, того, что было, когда изображение первоначально выполнялось). Однако такой подход невозможен, поскольку реализациям разрешено завершать вызовы * _init () и * _destroy () для мьютексов и блокировок, если мьютекс или блокировка все еще заблокированы. В этом случае дочерняя процедура не может повторно инициализировать мьютексы и блокировки.
В Linux эта тестовая программа C возвращает EPERM из pthread_mutex_unlock () в дочернем обработчике pthread_atfork (). Linux требует добавления _NP в макрос PTHREAD_MUTEX_ERRORCHECK для его компиляции.
Эта программа связана с этой хорошей веткой .
Учитывая, что технически небезопасно или законно открывать или уничтожать мьютекс у ребенка, я думаю, что лучше иметь указатели на мьютексы, а затем заставить ребенка создавать новый pthread_mutex_t в куче и оставлять мьютексы родителей в покое, тем самым небольшая утечка памяти.
Единственная проблема заключается в том, как повторно инициализировать состояние библиотеки, и я подумываю сбросить pthread_once_t. Может быть, потому что POSIX имеет инициализатор для pthread_once_t, что он может быть сброшен в исходное состояние.
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
static pthread_once_t once_control = PTHREAD_ONCE_INIT;
static pthread_mutex_t *mutex_ptr = 0;
static void
setup_new_mutex()
{
mutex_ptr = malloc(sizeof(*mutex_ptr));
pthread_mutex_init(mutex_ptr, 0);
}
static void
prepare()
{
pthread_mutex_lock(mutex_ptr);
}
static void
parent()
{
pthread_mutex_unlock(mutex_ptr);
}
static void
child()
{
// Reset the once control.
pthread_once_t once = PTHREAD_ONCE_INIT;
memcpy(&once_control, &once, sizeof(once_control));
}
static void
init()
{
setup_new_mutex();
pthread_atfork(&prepare, &parent, &child);
}
int
my_library_call(int arg)
{
pthread_once(&once_control, &init);
pthread_mutex_lock(mutex_ptr);
// Do something here that requires the lock.
int result = 2*arg;
pthread_mutex_unlock(mutex_ptr);
return result;
}
В приведенном выше примере в child () я только сбрасываю pthread_once_t, делая копию свежего pthread_once_t, инициализированного PTHREAD_ONCE_INIT. Новый pthread_mutex_t создается только тогда, когда библиотечная функция вызывается в дочернем процессе.
Это хакерский, но, возможно, лучший способ справиться с этим, обходя стандарты. Если pthread_once_t содержит мьютекс, то система должна иметь способ инициализации его из своего состояния PTHREAD_ONCE_INIT. Если он содержит указатель на мьютекс, выделенный в куче, он будет вынужден выделить новый и установить адрес в pthread_once_t. Я надеюсь, что он не использует адрес pthread_once_t для чего-то особенного, что могло бы победить это.
Поиск
Группа comp.programming.threads для pthread_atfork () показывает много хороших обсуждений и то, как мало стандартов POSIX действительно предоставляет для решения этой проблемы.
Существует также проблема, заключающаяся в том, что нужно вызывать функции async-signal-safe только из обработчиков pthread_atfork (), и, по-видимому, наиболее важным является дочерний обработчик , где выполняется только memcpy () .
Это работает? Есть ли лучший способ справиться с требованиями нашей общей библиотеки?