linux实验多进程,Linux开发简单多进程应用

一个进程中的多个线程共享一个进程的堆等内存空间,所以实现数据交互是很方便的;但多进程架构中,要想实现多进程间的数据交互相对就困难很多!

进程间通信(IPC,InterProcess Communication)是指在不同进程之间传递或交换信息。IPC常见的方式有:管道(无名管道、命名管道)、消息队列、信号量、共享内存、磁盘文件、Socket等,其中Socket网络方式可以实现不同主机上的多进程IPC

一个带坑的小例子

下面使用fork()、pipe()实现一个简单的Linux平台下的多进程、多进程通信的程序

#include

#include

#include

int main()

{

int pipefd[2]; //两个文件描述符

pid_t pid;

char buff[20];

int i;

//创建管道

if(0 > pipe(pipefd)){

printf("Create Pipe Error!\n");

}

//创建2个子进程

for(i=0; i<2; i++){

pid = fork();

if((pid_t)0 >= pid){

sleep(1);

break;

}

}

//如果fork返回值小于0,说明创建子进程失败

if((pid_t)0 > pid){

printf("Fork Error!\n");

return 3;

}

//如果fork返回值为0,表示进入子进程的逻辑

if((pid_t)0 == pid){

//发送格式化数据给主进程

FILE *f;

f = fdopen(pipefd[1], "w");

fprintf(f, "I am %d\n", getpid());

fclose(f);

sleep(1);

//接收父进程发过来的数据

read(pipefd[0], buff, 20);

printf("MyPid:%d, Message:%s", getpid(), buff);

}

//进入父进程的逻辑

else{

//循环接收所有子进程发过来的数据,并且返回数据给子进程

for(i=0; i<2; i++){

//接收子进程发来的数据

read(pipefd[0], buff, 20);

printf("MyPid:%d, Message:%s", getpid(), buff);

//发送数据给子进程

write(pipefd[1], "Hello My Son\n", 14);

}

//这里调用sleep(3)主要的作用是等待子进程运行结束

//当然这样并不是很规范!

sleep(3);

}

return 0;

}

编译程序gcc process.c -o process,然后执行./process输入信息如下

bVJ0PI?w=527&h=165

但我们可以发现输出的内容有一些异常,比如第二行最开始怎么有一个@字符、最后一行明显丢失了一些字符信息等

上面的程序还不止是输出不符合预期这个表面的问题,还存在诸多的坑,都是因为一开始对于多进程、管道的深入机制理解不正确造成的!

下面针对Linux的管道进行比较深入的挖掘,就可以发现上面的小程序中存在很多的坑

管道是阻塞的

管道读写是阻塞的,当管道中没有数据,但进程尝试去读的时候就会阻塞进程,比如

#include

#include

#include

int main()

{

int pipefd[2];

pid_t pid;

char buff[20];

int i;

if(0 > pipe(pipefd)){

printf("Create Pipe Error!\n");

}

pid = fork();

if((pid_t)0 > pid){

printf("Fork Error!\n");

return 3;

}

if((pid_t)0 == pid){

//write(pipefd[1], "Hello\n", 6);

}

else{

read(pipefd[0], buff, 20);

printf("MyPid:%d, Message:%s", getpid(), buff);

}

return 0;

}

其运行效果如下,可以看到主进程阻塞住了

bVJ0P0?w=509&h=99

可以修改让子进程往管道中写入数据,主进程再去读,这样就不会阻塞了

#include

#include

#include

int main()

{

int pipefd[2];

pid_t pid;

char buff[20];

int i;

if(0 > pipe(pipefd)){

printf("Create Pipe Error!\n");

}

pid = fork();

if((pid_t)0 > pid){

printf("Fork Error!\n");

return 3;

}

if((pid_t)0 == pid){

}

else{

read(pipefd[0], buff, 20);

printf("MyPid:%d, Message:%s", getpid(), buff);

}

return 0;

}

运行程序可以看到主进程没有阻塞

bVJ0P5?w=556&h=98

管道是半双工的

所谓半双工,意思就是只能在一个方向上传递数据,对于pipe,只能从pipe[1]写,从pipe[0]读,只能在一个方向传递数据;可以结合socket来理解,socket是全双工的,也就是针对一个socket既可以写又可以读

第一个例程中创建了一个管道,但却希望通过这个管道实现主进程传递数据给子进程、子进程传递数据给主进程,完全是想在两个方向传递数据,结果导致主进程和2个子进程同时既往管道里写,又从管道里读,所以出现了上述诡异的现象

比如下面这个例子,创建一个管道,但不创建子进程,可以在主进程既写又读管道!

#include

#include

#include

int main()

{

int pipefd[2];

pid_t pid;

char buff[20];

int i;

if(0 > pipe(pipefd)){

printf("Create Pipe Error!\n");

}

write(pipefd[1], "Hello\n", 6);

read(pipefd[0], buff, 20);

printf("MyPid:%d, Message:%s", getpid(), buff);

return 0;

}

bVJ0Qf?w=524&h=145

因为管道是半双工的,所以要想在保证数据不乱的情况下,不能在多进程应用中只使用一个管道,需要一套管道,有的是数据从主进程到子进程,有的是数据从子进程到主进程

完善后的程序

#include

#include

#include

int main()

{

//管道1,用于子进程发送数据给主进程

int pipefd[2];

//管道数组2,用于主进程分别发数据给子进程

int pipearr[3][5];

pid_t pid;

char buff[20];

int i;

//创建管道

if(0 > pipe(pipefd)){

printf("Create Pipe Error!\n");

}

for(i=0; i<3; i++){

if(0 > pipe(pipearr[i])){

printf("Create Pipe Error!\n");

}

}

//创建3个子进程

for(i=0; i<3; i++){

pid = fork();

//创建子进程失败

if((pid_t)0 > pid){

printf("Fork Error!\n");

return 3;

}

//子进程逻辑

if((pid_t)0 == pid){

//发送格式化数据给主进程

FILE *f;

f = fdopen(pipefd[1], "w");

fprintf(f, "I am %d\n", getpid());

fclose(f);

//接收父进程发过来的数据

read(pipearr[i][0], buff, 20);

printf("MyPid:%d, Message:%s", getpid(), buff);

//完成后及时退出循环,继续循环会出大问题,和fork的运行逻辑有关!

break;

}

}

//主进程逻辑

if((pid_t)0 < pid){

//循环接收所有子进程发过来的数据,并且返回数据给子进程

for(i=0; i<3; i++){

//接收子进程发来的数据

read(pipefd[0], buff, 20);

printf("MyPid:%d, Message:%s", getpid(), buff);

//发送数据给子进程

write(pipearr[i][6], "Hello My Son\n", 14);

}

sleep(3);

}

return 0;

}

编译后的运行效果如下:

bVJ0Ql?w=553&h=189

简单说一下上面的程序逻辑:

首先是有两种管道

第一种只有一个:创建的3个子进程都往这里面写,主进程从这里面读取数据

第二种有一组,每个子进程一个:主进程分别往3个管道中写,每个子进程对应从属于自己的管道中读

针对第二种,很明显一个写,一个读,可以保证并发安全。但第一种呢,多个子进程都往一个管道里面写,会不会有问题,这个需要特别注意:

当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性

当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性

上面多个子进程同时往pipefd中写入的数据小于PIPE_BUF,所以是原子性的,另外只有主进程一个进程在读,所以可以保证数据的完整性。在webbench中其实就是这样使用管道的

可以编译下面的程序,查看PIPE_BUF的值

#include

#include

int main()

{

printf("PIPE_BUF = %d\n", PIPE_BUF);

return 0;

}

编译后运行效果如下:

bVJ0Qs?w=582&h=136

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值