【问题标题】:Forking while holding a lock持锁分叉
【发布时间】:2026-01-28 06:05:03
【问题描述】:

我有以下程序:

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <iostream>


int main() {
   pthread_mutex_t lock_;
   pthread_mutexattr_t ma;
   pthread_mutexattr_init( &ma );
   pthread_mutexattr_setpshared( &ma, PTHREAD_PROCESS_SHARED );
   pthread_mutexattr_settype( &ma, PTHREAD_MUTEX_ERRORCHECK );
   pthread_mutex_init( &lock_, &ma );

   pthread_mutex_lock( &lock_ );

   if(fork()==0) {
      std::cout << "child" << std::endl;
      pthread_mutex_lock( &lock_ );
      std::cout << "finish" << std::endl;
   } else {
      std::cout << "parent" << std::endl;
      sleep(1);
      pthread_mutex_lock( &lock_ );
      std::cout << "parent done" << std::endl;
   }

}

我看到的行为是父母可以重新锁定互斥锁,但不能重新锁定孩子。我本来希望 fork() 分叉当前线程的所有上下文,所以孩子最终会得到一个它已经锁定的锁(IE,我不想共享锁 - 两个进程都有自己的锁是我想要的)。为什么这不起作用/我该如何完成?

【问题讨论】:

  • 那么就没有办法让当前线程锁定的互斥量,而在fork之后,子进程和父进程的互斥量都在那个进程中工作?
  • 在初始化期间设置错误检查时是否检查了 pthread_mutex_lock 的返回码?在父级第二次调用 pthread_mutex_lock 时应该返回错误。

标签: c++ multithreading


【解决方案1】:

它不起作用只是因为它被明确记录为不起作用,以一种有点令人困惑的方式。 fork() 和多线程进程不能很好地协同工作。

虽然fork() 手册页最初声称它“通过复制调用进程创建一个新进程”,但这是一个小小的善意谎言。如果fork() 真的这样做并复制了整个进程,它必须忠实地复制所有进程的执行线程。因为这就是您的流程的全部意义所在:构成整个流程的所有执行线程。

但事实并非如此。

如果您keep reading the fork(2) manual page,您将进入此部分:

子进程是用一个线程创建的——那个线程 调用 fork()。

这将是您的鸣喇叭线索编号 #1,fork() 并没有真正复制整个过程。它只复制它的一个执行线程。

父级的整个虚拟地址空间是 在孩子中复制,包括互斥体的状态、条件 变量和其他 pthreads 对象;

现在,停下来想想这意味着什么:只有一个执行线程被分叉,但所有这些都在子进程中精心复制。我们必须从中得出的结论相当丑陋。

如果您考虑一下:子进程继续使用一个单独的执行线程,其互斥锁最初被其他线程锁定。但是子进程中不存在其他线程。它不是分叉的。只派生了一个线程。

这其实在pthread_atfork(3) manual page中已经澄清了:

例如,在调用 fork(2) 时,其他线程可能 已锁定在用户空间内存中可见的互斥锁 在孩子身上重复。这样的互斥锁永远不会被解锁,因为 放置锁的线程不会在子进程中重复。

所以最重要的是,在您的示例代码中,子进程最终会拿着一个袋子,其中包含一堆在子进程中不再处于任何有效状态的互斥体。

【讨论】:

  • 对,我读到了 - 这似乎暗示如果线程 A 锁定互斥锁,然后线程 B 分叉,这是一个问题,但是如果线程 A 锁定然后线程 A 分叉,它会起作用.