进程分叉后RAII对象会发生什么?

Seb*_*ach 13 c++ unix linux fork

在Unix/Linux下,我的活动RAII对象在分叉时会发生什么?会有双重删除吗?什么是复制构造和分配?如何确保没有发生任何不良事件?

Nik*_*sov 13

fork(2)创建进程的完整副本,包括其所有内存.是的,自动对象的析构函数将在父进程和子进程中运行两次,位于不同的虚拟内存空间中.没有什么"坏"发生(当然,除非你从析构函数中的帐户中扣除钱),你只需要知道这个事实.

  • 对于仅内存对象,甚至是包含简单文件描述符的对象,是的.事情很简单.内存与写入时复制和带有"dup()"的描述符重复.然而,析构函数可能会做更具破坏性的事情,比如在套接字上执行`shutdown()`.这对父母/孩子来说并不安全. (6认同)

Seb*_*ach 5

原则上,在C++中使用这些函数没有问题,但您必须知道共享哪些数据以及如何共享.

考虑一下,fork()新进程获取父进程内存的完整副本(使用copy-on-write).内存是状态,因此你有两个独立的进程必须留下干净的状态.

现在,只要你保持在给予你的记忆范围内,你就不应该有任何问题:

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

class Foo {
public:
    Foo ()  { std::cout << "Foo():" << this << std::endl; }
    ~Foo()  { std::cout << "~Foo():" << this << std::endl; }

    Foo (Foo const &) {
        std::cout << "Foo::Foo():" << this << std::endl;
    }

    Foo& operator= (Foo const &) {
        std::cout << "Foo::operator=():" << this<< std::endl;
        return *this;
    }
};

int main () {
    Foo foo;
    int pid = fork();
    if (pid > 0) {
        // We are parent.
        int childExitStatus;
        waitpid(pid, &childExitStatus, 0); // wait until child exits
    } else if (pid == 0) {
        // We are the new process.
    } else {
        // fork() failed.
    }
}
Run Code Online (Sandbox Code Playgroud)

以上程序将大致打印:

Foo():0xbfb8b26f
~Foo():0xbfb8b26f
~Foo():0xbfb8b26f
Run Code Online (Sandbox Code Playgroud)

不会发生复制构造或复制分配,操作系统将进行按位复制.地址是相同的,因为它们不是物理地址,而是指向每个进程的虚拟内存空间的指针.

当两个实例共享信息时变得更加困难,例如在退出之前必须刷新和关闭的打开文件:

#include <iostream>
#include <fstream>

int main () {
    std::ofstream of ("meh");
    srand(clock());
    int pid = fork();
    if (pid > 0) {
        // We are parent.
        sleep(rand()%3);
        of << "parent" << std::endl;
        int childExitStatus;
        waitpid(pid, &childExitStatus, 0); // wait until child exits
    } else if (pid == 0) {
        // We are the new process.
        sleep(rand()%3);
        of << "child" << std::endl;
    } else {
        // fork() failed.
    }
}
Run Code Online (Sandbox Code Playgroud)

这可能会打印

parent
Run Code Online (Sandbox Code Playgroud)

要么

child
parent
Run Code Online (Sandbox Code Playgroud)

或者是其他东西.

问题是这两个实例不足以协调它们对同一文件的访问,并且您不知道实现细节std::ofstream.

(可能的)解决方案可以在"进程间通信"或"IPC"这两个术语下找到,最近的解决方案是waitpid():

#include <unistd.h>
#include <sys/wait.h>

int main () {
    pid_t pid = fork();
    if (pid > 0) {
        int childExitStatus;
        waitpid(pid, &childExitStatus, 0); // wait until child exits
    } else if (pid == 0) {
        ...
    } else {
        // fork() failed.
    }
}
Run Code Online (Sandbox Code Playgroud)

最简单的解决方案是确保每个进程仅使用自己的虚拟内存,而不是其他任何内容.

另一种解决方案是Linux特定的解决方案:确保子流程不会清理.操作系统将对所有获取的内存进行原始的非RAII清理,并关闭所有打开的文件,而不会刷新它们.如果您使用fork()exec()来运行另一个进程,这可能很有用:

#include <unistd.h>
#include <sys/wait.h>

int main () {
    pid_t pid = fork();
    if (pid > 0) {
        // We are parent.
        int childExitStatus;
        waitpid(pid, &childExitStatus, 0);
    } else if (pid == 0) {
        // We are the new process.
        execlp("echo", "echo", "hello, exec", (char*)0);
        // only here if exec failed
    } else {
        // fork() failed.
    }
}
Run Code Online (Sandbox Code Playgroud)

退出而不触发任何更多析构exit()函数的另一种方法是函数.我通常建议不要在C++中使用,但在分叉时,它有它的位置.


参考文献: