Unix 中的线程和分叉进程有什么区别?

What is the difference between threads and forked processes in Unix?

我知道分叉进程不共享内存,而线程共享内存,但是分叉​​进程如何相互通信?

这里是一个例子,其中一个带线程的版本被注释掉了(那个版本将结束),而另一个带分支的版本将永远不会结束。该代码依赖于全局变量 done:

#include <stdio.h>
#include <stdbool.h>
#include <signal.h>
#include <unistd.h>
#include <pthread.h>

bool done = false;

void *foo(void *arg){
    sleep(1);
    done = true;
    return 0;
}

int main(){
    //pthread_t t1;
    //pthread_create(&t1, NULL, foo, NULL);
//
    //printf("waiting...\n");
    //while(!done){}
    //printf("Ok. Moving on.\n");
    
    printf("waiting...\n");
    if(!fork()){
        foo(NULL);
    } else {
        while(!done){}
        printf("OK. moving on.\n");
    }
}

因此,如果分叉进程不像线程那样不共享数据(即全局变量?),那么它们在 unix 中如何通信?

编辑: 这绝对不是重复的,因为我已经在 *nix 中看到类似的主题,如 Forking vs Threading 和其他关于 fork/threads 的文档。我只想知道两者的用例。 (例如 windows 没有分支,只有线程,所以他们可能考虑了不同的用例?)

当您执行 fork 时,您使用不同的 PID 创建正在执行的进程的副本,在 fork() 执行之前声明的变量将出现在两个进程中。 fork returns 0 在“子”进程中 returns pid 在“父”进程中的“子”进程(带有 switch, 你可以控制两个进程的行为).

如果您想与 fork() 创建的不同进程进行通信,您可以在文件描述符数组(例如 int fd[2] 之前声明并执行 pipe(fd)。如果 pipe 的结果不是 -1,则表示您创建了两条“电缆”,您可以在其中获取 writeread 信息。

Here 您可以查看有关其工作原理的示例

fork()复制当前进程。在没有任何特殊准备的情况下,child和parent之间几乎没有数据交换。只是为了使新进程与旧进程相同,但是一旦您写入变量,就会创建写入区域的副本,并且 child 会为该数据获取一个新的物理内存位置。这意味着 child 中设置的变量对 parent 不可见,反之亦然。

您可以使用共享内存、管道、文件、套接字、信号以及可能的其他 IPC 方法在 child 和 parent 之间进行通信。对于您的特殊情况,您可以使用 wait()waitpid() 函数等待 child 退出。但我假设您想知道如何交换数据。

共享内存

您可以使用 mmap() 调用来保留 parent 和 child 之间共享的内存。

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

您可以将标志 MAP_SHARED | MAP_ANONYMOUS 传递给 flags 以创建共享的内存区域。您可以在那里放置共享变量,并且两者都可以访问它。这是一个例子。

//creates a region of shared memory to store a bool
static bool *reserveSharedMemory(void)
  {
    void *data = mmap(NULL, sizeof(bool), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if(MAP_FAILED==data) 
      {
        //do some error handling here
        return NULL;
      }
    bool *p=data;
    *p=false;
    return p;  
  }

套接字

套接字允许您使用其他方式发送和接收数据。使用 socketpair() 你可以创建 2 个套接字文件描述符,你可以通过写入其中一个并读取另一个文件描述符或 verse visa 来进行通信。通过这种方式,与 child 进程的通信几乎与与网络套接字的通信相同。

您可能已经知道,分叉线程是调用 fork() 的主线程的子线程,它使用地址 space 和文件描述符 [=13= 的副本进行初始化] 的父亲,同时它共享打开的文件 table。正如有人已经说过的那样,当您只能创建一个新线程时,使用分叉线程并没有什么意义,那是因为拥有同一线程的两个副本从来都不是一个好主意。 我想说明的是,您可以创建一个分叉线程,它使用“vfork”与父亲共享所有数据,但这个线程真的被弃用了,我添加它只是作为附加信息。 如果需要,您可以使用管道、套接字等在父子之间进行通信,并且可以通过检查 pid 来确定您是在父线程还是子线程上。