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

时间:2016-10-02 00:11:02

标签: c linux pthreads fork posix

序言

标记的"欺骗"以上回答我的问题,因为它涉及使用线程分叉的安全性。我没有在我的代码中生成线程,并且更关心pthread_mutex_t结构及其在fork()发生fork()时在操作系统中注册的内部结构的有效性,即:是在子进程中的pthread_mutex_t上重新创建互斥锁,或者孩子只是拥有父虚拟内部的有效(或无效)浅表副本?

背景

我有一个音频/硬件处理库,它使用递归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); } 通过简单的API包装一些DSP函数。它是递归互斥的原因是因为一些API函数依次调用其他API函数,我想确保只有一个线程进入每个库实例的临界区。所以,代码看起来像这样:

fork()

问题

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

注意:我可以保证在发出var links1 = link2 = link3 = link4 = link5 = ["a.html","b.html","c.html","d.html","e.html","f.html","g.html"] function myLinkJS(){ document.write( '<a href=\"'+link1+'\" ></a>\n' ); document.write( '<a href=\"'+link2+'\" ></a>\n' ); document.write( '<a href=\"'+link3+'\" ></a>\n' ); document.write( '<a href=\"'+link4+'\" ></a>\n' ); } 来电时,互联网会被锁定。

谢谢。

3 个答案:

答案 0 :(得分:2)

WRT。 POSIX,我同意caf's answer,但幸运的是在Linux中,语义定义的。

来自Linux man 2 fork手册页:

  

使用单个线程创建子进程 - 调用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);
}

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

问题在于,通常无法证明在多线程程序中,当另一个线程执行fork()时,其他任何线程都没有执行任何上述函数。

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

答案 1 :(得分:1)

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

如果您同意此读数,那么最简单的解决方案是避免在单线程情况下使用互斥锁。您可以要求库用户链接到库的多线程版本或者具有#ifdef&#39; d out同步的单线程版本,具体取决于它们的用例。或者,不要自己调用pthread_mutex_lock() / pthread_mutex_unlock(),而是允许用户提供库调用的锁定/解锁回调 - 单线程代码只提供空回调。

答案 2 :(得分:1)

  

注意:我可以保证在发出fork()来电时,互联网会被锁定。

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

如果 使用线程,您可能希望pthread_create而不是fork,以便互斥锁被释放&#34;自然&#34; [通过持有线程]。

当您fork时,pthread互斥锁具有 no 有效性/含义。它需要一个共享的地址空间,在你分叉后,你不再拥有。因此,安全的赌注是重新启动孩子的互斥锁。但是,这个互斥锁不再与父进程或其兄弟进程一起运行。如果分支线程是互斥锁所有者,则init只是为了防止子进程阻塞。

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

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

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