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

C 单线程进程中的pthread互斥体是否需要在fork()上重新初始化? 序言,c,linux,pthreads,fork,posix,C,Linux,Pthreads,Fork,Posix,上面标记的“dupe”并没有回答我的问题,因为它涉及到使用线程进行分叉的安全性。我不是在我的代码中生成线程,我更关心的是pthread\u mutex\u t结构的有效性,以及在发生fork()时在操作系统中注册的结构及其内部,即:是在子进程中的fork()上重新创建的互斥体,还是子进程中只有有效(或无效)的互斥体父级互斥体内部的浅拷贝? 背景 我有一个音频/硬件处理库,它使用递归的pthread\u mutex\u t用一个简单的API封装了一些DSP函数。它之所以是递归互斥体,是因为一些A

上面标记的“dupe”并没有回答我的问题,因为它涉及到使用线程进行分叉的安全性。我不是在我的代码中生成线程,我更关心的是
pthread\u mutex\u t
结构的有效性,以及在发生
fork()
时在操作系统中注册的结构及其内部,即:是在子进程中的
fork()
上重新创建的互斥体,还是子进程中只有有效(或无效)的互斥体父级互斥体内部的浅拷贝?


背景 我有一个音频/硬件处理库,它使用递归的
pthread\u mutex\u 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);
}

问题: 如果用户应用程序使用my library,并且应用程序发出
fork()
调用,则my library的子进程实例是否需要重新初始化其互斥体实例?我知道子进程不会继承线程,如果我想让两个进程真正共享互斥体(记不起该标志是什么),或者我必须使用
mmap
IIRC,则需要使用特定的互斥体初始化标志。但是子级使用的互斥实例是否有效(例如:
fork()
是否复制了内部值,但这些值不再有效,或者是否使用操作系统初始化了新的互斥体)当发生
fork()
时,我不希望子进程和父进程共享互斥体,但我希望确保客户端使用的是有效的互斥体句柄。

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


谢谢。

仔细阅读POSIX后发现它没有定义:

  • 说它创建了过程的精确副本
  • 表示锁定、解锁或销毁对象时引用对象副本的效果未定义
如果您同意这一点,那么最简单的解决方案就是在单线程情况下避免使用互斥锁。您可以要求库用户针对库的多线程版本或已完成同步的单线程版本进行链接,具体取决于他们的用例。或者,您可以允许用户提供库调用的锁定/解锁回调,而不是自己调用
pthread\u mutex\u lock()
/
pthread\u mutex\u unlock()
,单线程代码只提供空回调

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

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

如果您使用的是线程,那么您可能希望执行
pthread\u create
而不是
fork
,以便互斥锁可以[通过保持线程]自然释放

当您使用
fork
时,pthread互斥体没有有效性/意义。它需要一个共享的地址空间,在你分叉之后,你就不再有了。因此,安全的办法是在孩子身上重新设置互斥。但是,此互斥不再与父进程或其线程同级一起运行。init只是为了防止子线程在分叉线程不是互斥对象所有者时阻塞

要在线程之间锁定,请使用
pthread\u mutex.*
。要在进程之间锁定,请使用SysV信号量或posix信号量

根据代码示例中pthread mutex的使用情况,我怀疑您希望在给定的API调用期间以独占方式访问音频设备

如果你不使用线程,你已经有了没有互斥的线程。当您添加分叉时,互斥锁不起作用,因此您希望使用[named]信号量[并且您必须实现自己的包装器,因为IIRC,
sem\u wait
等。不要执行递归]

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

从Linux手册页:

子进程是用一个名为fork()的线程创建的。父对象的整个虚拟地址空间在子对象中复制,包括互斥体、条件变量和其他pthreads对象的状态;使用pthread_atfork(3)可能有助于处理这可能导致的问题

因此,在Linux中,如果互斥锁被解锁,则不需要在
fork()
之后重新初始化互斥锁


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

在父进程中打开的任何信号量也应在子进程中打开

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

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

如果您不使用线程,那么为什么首先需要
pthread\u mutex\u t
。@SamVarshavchik:这是一个库,可以在多线程进程或单线程进程中使用。@SamVarshavchik caf称之为:@谢谢你们的深入回答。这个问题是关于一个库的,它可以用于多线程进程(在这种情况下,作者希望确保库中一次只执行一个线程),也可以用于单线程进程,在这种情况下,互斥体是不必要的,但无害的。作者还声明,他们不关心多个进程之间的排除。
#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);
}