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

Bla*_*jac 20 c multithreading pthreads ice

我们有一个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 <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;
}
Run Code Online (Sandbox Code Playgroud)

在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().

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

R..*_*R.. 22

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

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

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

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


jil*_*les 8

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

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