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

时间:2010-04-12 06:47:18

标签: c multithreading pthreads ice

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

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

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

pthread_atfork()上阅读关于处理互斥和内部状态的POSIX标准:

或者,某些库可能只能提供一个子例程,它将库中的互斥锁和所有关联状态重新初始化为某个已知值(例如,最初执行映像时的状态)。但是,这种方法是不可能的,因为如果互斥锁或锁仍然被锁定,则允许实现失败* _init()和* _destroy()调用互斥锁和锁。在这种情况下,子例程无法重新初始化互斥锁和锁。

在Linux上,这个test C program从子pthread_atfork()处理程序中的pthread_mutex_unlock()返回EPERM。 Linux需要将_NP添加到PTHREAD_MUTEX_ERRORCHECK宏中才能进行编译。

此程序与此good thread链接。

鉴于在解锁或销毁子进程中的互斥锁在技术上不安全或合法,我认为最好有指向互斥锁的指针,然后让子进程在堆上创建新的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包含互斥锁,则系统必须具有从PSTREAD_ONCE_INIT状态初始化它的方法。如果它包含指向堆上分配的互斥锁的指针,那么它将被强制分配一个新的并在pthread_once_t中设置地址。我希望它不会使用pthread_once_t的地址来解决任何特殊的问题。

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

还有一个问题是,人们只应该从pthread_atfork()处理程序调用异步信号安全函数,并且它出现most important one is the child handler,其中只有memcpy()。

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

2 个答案:

答案 0 :(得分:22)

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

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

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

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

答案 1 :(得分:7)

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

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