我们有一个 C++ 共享库,它使用ZeroC 的 Ice库进行 RPC,除非我们关闭 Ice 的运行时,否则我们会观察到子进程挂在随机互斥体上。Ice 运行时启动线程,有许多内部互斥体,并为服务器保留打开的文件描述符。
此外,我们有一些自己的互斥锁来保护我们的内部状态。
我们的共享库被数百个内部应用程序使用,因此我们无法控制进程何时调用 fork(),因此我们需要一种方法来安全地关闭 Ice 并在进程 fork 时锁定我们的互斥锁。
在pthread_atfork()上阅读关于处理互斥锁和内部状态的 POSIX 标准:
或者,一些库可能只提供一个子例程,将库中的互斥锁和所有相关状态重新初始化为某个已知值(例如,最初执行图像时的状态)。但是,这种方法是不可能的,因为如果互斥锁或锁仍处于锁定状态,则允许实现对互斥锁和锁的 *_init() 和 *_destroy() 调用失败。在这种情况下,子例程无法重新初始化互斥锁和锁。
在 Linux 上,此测试 C 程序从子 pthread_atfork() 处理程序中的 pthread_mutex_unlock() 返回 EPERM。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_INIT 初始化的新 pthread_once_t 来重置 pthread_once_t。只有在子进程中调用库函数时才会创建新的 pthread_mutex_t。
这是 hacky 但也许是处理这个绕过标准的最好方法。如果 pthread_once_t 包含一个互斥体,那么系统必须有一种方法可以从其 PTHREAD_ONCE_INIT 状态对其进行初始化。如果它包含指向在堆上分配的互斥锁的指针,那么它将被迫分配一个新的并在 pthread_once_t 中设置地址。我希望它不会将 pthread_once_t 的地址用于任何可以解决此问题的特殊情况。
在 comp.programming.threads 组中搜索pthread_atfork()显示了很多很好的讨论,以及 POSIX 标准真正为解决这个问题提供了多少。
还有一个问题是应该只从 pthread_atfork() 处理程序调用异步信号安全函数,而且似乎最重要的是子处理程序,其中只完成了一个 memcpy()。
这行得通吗?有没有更好的方法来处理我们共享库的要求?