如何使用pthread_atfork()和pthread_once()重新初始化子进程中的互斥锁

我们有一个C ++共享库,它使用ZeroC的Ice库来实现RPC,除非我们关闭Ice的运行时,否则我们观察到子进程挂在随机互斥锁上。 Ice运行时启动线程,具有许多内部互斥锁并保持打开文件描述符到服务器。

另外,我们有一些我们自己的互斥体来保护我们的内部状态。

我们的共享库被数百个内部应用程序使用,因此我们无法控制进程何时调用fork(),因此我们需要一种方法来安全地关闭Ice并在进程分叉时锁定我们的互斥锁。

在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  #include  #include  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包含互斥锁,则系统必须具有从PSTREAD_ONCE_INIT状态初始化它的方法。 如果它包含指向堆上分配的互斥锁的指针,那么它将被强制分配一个新的并在pthread_once_t中设置地址。 我希望它不会使用pthread_once_t的地址来解决任何特殊问题。

在pthread_atfork()中搜索comp.programming.threads组显示了很多很好的讨论,以及POSIX标准为解决这个问题提供了多少帮助。

还有一个问题是,人们只应该从pthread_atfork()处理程序调用异步信号安全函数,并且看起来最重要的是子处理程序 ,其中只有memcpy()。

这有用吗? 有没有更好的方法来处理我们的共享库的要求?

恭喜,您发现标准存在缺陷。 pthread_atfork从根本上无法解决用互斥体解决它所创建的问题,因为子进程中的处理程序不允许对它们执行任何操作:

  • 它无法解锁它们,因为调用者将是新创建的子进程中的新主线程,并且该线程与获取锁定的线程(在父级中)不同。
  • 它不能摧毁它们,因为它们被锁定了。
  • 它无法重新初始化它们,因为它们没有被破坏。

一种可能的解决方法是在此处使用POSIX信号量代替互斥量。 信号量没有所有者,因此如果父进程将其锁定( sem_wait ),则父进程和子进程都可以解锁( sem_post )其各自的副本,而不会调用任何未定义的行为。

作为一个好的方面, sem_post是异步信号安全的,因此对孩子来说绝对合法。

我认为这是调用fork()的程序中的一个错误。 在multithreading进程中,子进程应仅调用异步信号安全函数。 如果程序想要在没有exec的情况下进行分叉,那么它应该在创建线程之前执行。

对于线程fork()/ pthread_atfork(),没有一个很好的解决方案。 它的一些块似乎可以工作,但这不是可移植的,并且可能会破坏OS版本。