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
,则表示您创建了两条“电缆”,您可以在其中获取 write
或 read
信息。
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 来确定您是在父线程还是子线程上。
我知道分叉进程不共享内存,而线程共享内存,但是分叉进程如何相互通信?
这里是一个例子,其中一个带线程的版本被注释掉了(那个版本将结束),而另一个带分支的版本将永远不会结束。该代码依赖于全局变量 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
,则表示您创建了两条“电缆”,您可以在其中获取 write
或 read
信息。
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 来确定您是在父线程还是子线程上。