单线程进程中的 pthread 互斥量是否需要在 fork() 上重新初始化?

Do pthread mutexes in a single-threaded process need to be re-initialized on fork()?

前言

上面标记的“欺骗”没有回答我的问题,因为它涉及使用线程分叉的安全性。我没有在我的代码中生成线程,并且更关心 pthread_mutex_t 结构的有效性及其在 fork() 发生 . 时注册到 OS 的内部结构,即:是否在子进程内 fork() 上重新创建了互斥体,或者子进程是否仅具有父互斥体内部的有效(或无效)浅表副本?


背景

我有一个 audio/hardware 处理库,它使用递归 pthread_mutex_t 用一个简单的 API 包装了一些 DSP 函数。它是递归互斥锁的原因是因为一些 API 函数依次调用其他 API 函数,我想确保只有一个线程进入库的每个实例的临界区。所以,代码看起来像这样:

static pthread_mutex_t mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;

void read() {
    pthread_mutex_lock(&mutex);
    // ...
    pthread_mutex_unlock(&mutex);
}

void write() {
    pthread_mutex_lock(&mutex);
    // ...
    pthread_mutex_unlock(&mutex);
}

void toggle() {
    pthread_mutex_lock(&mutex);
    read();
    // ...
    write();
    pthread_mutex_unlock(&mutex);
}

问题

如果用户应用程序使用我的库,并且该应用程序发出 fork() 调用,我的库的子进程实例是否需要重新初始化其互斥体实例?我知道子进程不继承线程,如果我希望两个进程真正共享互斥锁(不记得标志是什么)或者我必须使用 mmap IIRC。但是子进程使用的互斥锁实例是否有效(即:fork() 是否复制了内部值,但它们不再有效,或者是使用 OS 初始化的新互斥锁)? 我不希望子进程和父进程在 fork() 发生时共享互斥锁,但我想确保客户端使用的是有效的互斥锁句柄。

注意:我可以保证在发出 fork() 调用时,互斥体 不会 被锁定。

谢谢。

仔细阅读 POSIX 表明它是未定义的:

如果您同意这篇文章,那么最简单的解决方案就是在单线程情况下完全避免使用互斥锁。您可以要求库用户 link 针对库的多线程版本或具有 #ifdef 同步的单线程版本,具体取决于他们的用例。或者,您可以允许用户提供您的库调用的锁定/解锁回调,而不是自己调用 pthread_mutex_lock() / pthread_mutex_unlock() - 单线程代码只会提供空回调。

Note: I can guarantee that the mutex will not be locked when the fork() call is issued.

为了保证这一点,这意味着您[如您所说]没有使用线程。否则,您无法保证这一点。所以,我同意山姆的观点。您不需要使用互斥量。

如果你使用线程,你可能想做pthread_create而不是fork,这样互斥就会被释放"naturally" [被抱线].

当您 fork 时,pthread 互斥量 没有 validity/meaning。它需要一个共享地址 space,在你 fork 之后,你就不再拥有了。因此,安全的做法是重新初始化 child 中的互斥量。但是,此互斥锁不再与 parent 进程或其线程兄弟一起运行。如果分叉线程 不是 互斥锁所有者,init 只是为了防止 child 阻塞。

要在 个线程 之间锁定,请使用 pthread_mutex_*。要在 进程 之间锁定,请使用 SysV 信号量或 posix 信号量。

根据您在代码示例中使用的 pthread 互斥锁,我怀疑您希望在给定的 API 调用期间独占访问音频设备。

如果您不使用线程,那么您已经拥有 没有 互斥锁。当您添加分叉时,互斥体不起作用,因此您需要 [named] 信号量而不是 [并且您必须实现自己的包装器,因为 IIRC,sem_wait 等。阿尔。不要递归]

Wrt。 POSIX,我同意 ,但幸运的是在 Linux 中定义了语义

来自 Linux man 2 fork 手册页:

The child process is created with a single thread—the one that called fork(). The entire virtual address space of the parent is replicated in the child, including the states of mutexes, condition variables, and other pthreads objects; the use of pthread_atfork(3) may be helpful for dealing with problems that this can cause.

因此,在 Linux 中,如果互斥量已解锁,则不需要在 fork().

后重新初始化它们

更重要的是,POSIX 为信号量定义了相同的东西;那

Any semaphores that are open in the parent process shall also be open in the child process.

这意味着您可以用信号量替换递归互斥量;只需将您的代码替换为

#include <semaphore.h>

static sem_t  my_lock;

static void my_lock_init(void) __attribute__((constructor));
static void my_lock_init(void) {
    sem_init(&my_lock, 0, 1U);
}

void my_read() {
    sem_wait(&my_lock);

    // ...

    sem_post(&my_lock);
}

void my_write() {
    sem_wait(&my_lock);

    // ...

    sem_post(&my_lock);
}

void toggle() {
    sem_wait(&my_lock);

    // ...
    my_read();
    // ...
    my_write();
    // ...

    sem_post(&my_lock);
}

重新初始化已经初始化的信号量会导致未定义的行为。这意味着上面的工作,如果你能以某种方式证明 fork() 永远不会发生在你的代码执行第一个 sem_wait(&my_lock) 和最后一个 sem_post(&my_lock).

之间

问题是,通常不可能证明在多线程程序中,当另一个线程执行 fork().[= 时,其他线程的 none 正在执行上述任何功能。 25=]


在 Linux 中,内核 2.6 或更高版本,GNU C 库版本 2.5 或更高版本,pthreads 基于 NPTL,pthread 锁定原语在 futex() syscall 之上实现。

内核仅在线程被阻塞或等待时才知道 futex。其余时间,futex 只是一个普通的数据结构。 (内核使用 futex 的地址来区分它们;共享内存的地址被特殊处理。)

这意味着当使用 futex 时,您可以在 fork() 之后安全地重新初始化互斥量,只要您自己的代码在持有互斥量时不进行分叉。

举个例子——请记住,仅适用于 Linux 2.6 或更高版本,以及 GNU C 库 2.5 或更高版本:

#define  _GNU_SOURCE
#include <pthread.h>

static pthread_mutex_t  my_lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;

static void my_reinit(void)
{
    my_lock = (pthread_mutex_t)PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
}

static void my_init(void) __attribute__((constructor (65535)));
static void my_init(void)
{
    pthread_atfork(NULL, NULL, my_reinit);
}

void my_read()
{
    pthread_mutex_lock(&my_lock);

    // ...

    pthread_mutex_unlock(&my_lock);
}

void my_write()
{
    pthread_mutex_lock(&my_lock);

    // ...

    pthread_mutex_unlock(&my_lock);
}

void toggle() 
{
    pthread_mutex_lock(&my_lock);

    // ...
    my_read();
    // ...
    my_write();
    // ...

    pthread_mutex_unlock(&my_lock);
}