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

发布于 2024-08-28 07:52:17 字数 2931 浏览 6 评论 0原文

我们有一个 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;
}

在上面的 child() 示例中,我仅通过复制使用 PTHREAD_ONCE_INIT 初始化的新 pthread_once_t 来重置 pthread_once_t。仅当在子进程中调用库函数时才会创建新的 pthread_mutex_t。

这虽然很老套,但也许是处理这种绕过标准的最佳方法。如果 pthread_once_t 包含互斥锁,则系统必须有一种方法从 PTHREAD_ONCE_INIT 状态对其进行初始化。如果它包含指向堆上分配的互斥锁的指针,那么它将被迫分配一个新的互斥锁并在 pthread_once_t 中设置地址。我希望它不会将 pthread_once_t 的地址用于任何会破坏此操作的特殊内容。

搜索

还有一个问题,即只能从 pthread_atfork() 处理程序调用异步信号安全函数,并且出现 最重要的是子处理程序,其中只完成了 memcpy() 。

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

We have a C++ shared library that uses ZeroC's Ice library for RPC and unless we shut down Ice's runtime, we've observed child processes hanging on random mutexes. The Ice runtime starts threads, has many internal mutexes and keeps open file descriptors to servers.

Additionally, we have a few of mutexes of our own to protect our internal state.

Our shared library is used by hundreds of internal applications so we don't have control over when the process calls fork(), so we need a way to safely shutdown Ice and lock our mutexes while the process forks.

Reading the POSIX standard on pthread_atfork() on handling mutexes and internal state:

Alternatively, some libraries might have been able to supply just a child routine that reinitializes the mutexes in the library and all associated states to some known value (for example, what it was when the image was originally executed). This approach is not possible, though, because implementations are allowed to fail *_init() and *_destroy() calls for mutexes and locks if the mutex or lock is still locked. In this case, the child routine is not able to reinitialize the mutexes and locks.

On Linux, the this test C program returns EPERM from pthread_mutex_unlock() in the child pthread_atfork() handler. Linux requires adding _NP to the PTHREAD_MUTEX_ERRORCHECK macro for it to compile.

This program is linked from this good thread.

Given that it's technically not safe or legal to unlock or destroy a mutex in the child, I'm thinking it's better to have pointers to mutexes and then have the child make new pthread_mutex_t on the heap and leave the parent's mutexes alone, thereby having a small memory leak.

The only issue is how to reinitialize the state of the library and I'm thinking of reseting a pthread_once_t. Maybe because POSIX has an initializer for pthread_once_t that it can be reset to its initial state.

#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;
}

In the above sample in the child() I only reset the pthread_once_t by making a copy of a fresh pthread_once_t initialized with PTHREAD_ONCE_INIT. A new pthread_mutex_t is only created when the library function is invoked in the child process.

This is hacky but maybe the best way of dealing with this skirting the standards. If the pthread_once_t contains a mutex then the system must have a way of initializing it from its PTHREAD_ONCE_INIT state. If it contains a pointer to a mutex allocated on the heap than it'll be forced to allocate a new one and set the address in the pthread_once_t. I'm hoping it doesn't use the address of the pthread_once_t for anything special which would defeat this.

Searching
comp.programming.threads group for pthread_atfork()
shows a lot of good discussion and how little the POSIX standards really provides to solve this problem.

There's also the issue that one should only call async-signal-safe functions from pthread_atfork() handlers, and it appears the most important one is the child handler, where only a memcpy() is done.

Does this work? Is there a better way of dealing with the requirements of our shared library?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

素罗衫 2024-09-04 07:52:17

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

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

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

顺便说一句,sem_post 是异步信号安全的,因此对于孩子来说绝对是合法的。

Congratulations, you found a defect in the standard. pthread_atfork is fundamentally unable to solve the problem it was created to solve with mutexes, because the handler in the child is not permitted to perform any operations on them:

  • It cannot unlock them, because the caller would be the new main thread in the newly created child process, and that's not the same thread as the thread (in the parent) that obtained the lock.
  • It cannot destroy them, because they are locked.
  • It cannot re-initialize them, because they have not been destroyed.

One potential workaround is to use POSIX semaphores in place of mutexes here. A semaphore does not have an owner, so if the parent process locks it (sem_wait), both the parent and child processes can unlock (sem_post) their respective copies without invoking any undefined behavior.

As a nice aside, sem_post is async-signal-safe and thus definitely legal for the child to use.

微暖i 2024-09-04 07:52:17

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

对于线程 fork()/pthread_atfork() 来说,确实没有一个好的解决方案。它的某些部分似乎可以工作,但这不可移植并且容易跨操作系统版本破坏。

I consider this a bug in the programs calling fork(). In a multi-threaded process, the child process should call only async-signal-safe functions. If a program wants to fork without exec, it should do so before creating threads.

There isn't really a good solution for threaded fork()/pthread_atfork(). Some chunks of it appear to work, but this is not portable and liable to break across OS versions.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文