前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何创建多进程程序?(文末福利)

如何创建多进程程序?(文末福利)

作者头像
编程珠玑
发布2019-08-19 15:44:13
1.6K0
发布2019-08-19 15:44:13
举报
文章被收录于专栏:编程珠玑编程珠玑

来源:公众号【编程珠玑】

作者:守望先生

网站:https://www.yanbinghu.com

前言

在《对进程和线程的一些总结》已经介绍了进程和线程的区别,但是在C/C++中如何创建进程呢?或者说如何编写多进程的程序呢?

什么时候需要fork进程

一种可能见到的场景是在服务器程序中,一个请求到来后,为了避免服务器阻塞,fork出一个子进程处理请求,父进程仍然继续等待请求到来。但这种方式无疑开销会稍大。

另一种最常见的就是执行一个不同的程序,例如我们在shell终端执行一条命令,实际上就是bash(或者其他)调用fork之后,在执行exec族函数。

fork

一个现有的进程可以通过fork函数来创建一个新的进程,这个进程通常称为子进程。fork函数原型如下:

代码语言:javascript
复制
#include<unistd.h>
pid_t fork(void);

如果调用成功,它将返回两次,子进程返回值是0;父进程返回的是非0正值,表示子进程的进程id;如果调用失败将返回-1,并且置errno变量。

有的朋友可能常常会记不住返回0的时候到底是子进程还是父进程。这里教给大家一个方法。一个进程可以有多个子进程,但是一个子进程同一时刻最多只有一个父进程。子进程可以通过getppid获取父进程的进程id,但是父进程却没法获取,因此需要在fork后就得到子进程的进程id。

代码语言:javascript
复制
//公众号【编程珠玑】,博客 https://www.yanbinghu.com
#include<stdio.h>
#include<unistd.h>
int main(void)
{
    pid_t pid;
    char testVal[] = {};
    FILE *fp = fopen("test.txt","w");
    if(NULL == fp)
    {
        printf("open test.txt failed\n");
        return ;
    }
    if(-1 == (pid = fork()))//等于-1时表明fork出错
    {
        perror("fork error");
        return -1;
    }
    else if( == pid)//子进程
    {
        snprintf(testVal,,"I am child,father pid is %d\n",getppid());
        fprintf(fp,"%s",testVal);

    }
    else  //父进程
    {
        snprintf(testVal,,"I am parent,child pid is %d\n",pid);
        fprintf(fp,"%s",testVal);
    }
    printf("fork over,testVal is %s",testVal);
    //为了避免马上退出sleep一段时间
    sleep();
    fclose(fp);
    return ;
}

并在同目录下创建一个test.txt文件,运行结果:

代码语言:javascript
复制
fork over,testVal is I am parent,child pid is 
fork over,testVal is I am child,father pid is 

需要注意的是,不要对父进程先执行还是子进程先执行做任何假设,因为都有可能。所以,可能出现的运行结果并不一样。

fork到底做了什么

fork被调用后,子进程拥有父进程的副本,因此它拥有父进程的数据空间,堆栈等。但是由于fork之后通常会调用exec函数去执行与原进程不想关的程序,因此fork时直接拷贝父进程的副本显得没有必要。为了提高fork的效率,采用了一种写时复制的技术。即fork之后,子进程名义上拥有父进程的副本,但是实际上和父进程共用,只有当父子进程中有一个试图修改这些区域时,才会以页为单位创建一个真正的副本。

所以我们看到前面的示例程序中,父子进程都对testVal进程了修改,但是互不影响。因为它们修改了不同的区域。

子进程继承了父进程哪些属性?

由于子进程是父进程的一个副本,所以父进程有的属性,子进程也都有,这些属性包括

  • 打开的文件描述符
  • 会话ID
  • 根目录
  • 资源限制
  • 工作目录
  • 进程组ID
  • 控制终端
  • 环境

我们运行前面的示例程序之后,重新打开一个终端,找到打开test.txt文件的进程:

代码语言:javascript
复制
$ lsof test.txt
fork     root    r   REG  ,          test.txt
fork     root    r   REG  ,          test.txt

lsof命令的用法可以参考《如何查看linux中文件打开情况?

也可以观察进程打开的文件描述符:

代码语言:javascript
复制
$ ls -l /proc//fd
lrwx------  root root  Aug  :  -> /dev/pts/
lrwx------  root root  Aug  :  -> /dev/pts/
lrwx------  root root  Aug  :  -> /dev/pts/
lr-x------  root root  Aug  :  -> /data/workspaces/practices/c/test.txt

为什么这里要特别说明打开的文件描述符呢?试想以下两点:

  • 父子进程对同一个文件进行写,将共享文件偏移
  • 如果该描述符是一个socket描述符,父进程退出后,子进程仍然打开着,父进程再次启动,将会出现端口被占用的问题。

所以如果父子进程的其中一个使用了fclose关闭了文件描述符,实际上还有另外一个进程打开了test.txt文件。

与前面testVal不同的是,如果父子进程都对文件进行写,并不会产生两个不同的文件,而是会对同一个文件进行写,因此运行后会在同一个文件里出现父子进程写的内容:

代码语言:javascript
复制
$ cat test.txt
I am parent,child pid is 
I am child,father pid is 

父子进程有哪些不同?

  • fork之后的返回值不同,进程ID也不同
  • 子进程未处理信号设置为空
  • 子进程不继承父进程设置的文件锁
  • 一般子进程会执行与父进程不完全一样的代码流程

总结

fork用于创建进程,但是需要注意的是,子进程继承了很多父进程的东西,如果子进程不需要可以进行修改或“丢弃”,例如子进程关闭父进程打开的文件描述符等等。理解了fork的写时复制思想,也就会明白,实际上fork的速度是非常快的。本文总结点如下:

  • fork调用一次,返回两次
  • 一个进程可以有多个子进程,但同一时刻最多只有一个父进程
  • 子进程继承了父进程很多属性
  • 父子进程执行的先后顺序不一定

本文仅仅简单介绍了fork,实际上得到子进程之后,还需要对子进程的状态进行“监控”,否则会出现其他意想不到的问题。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-08-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程珠玑 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 什么时候需要fork进程
  • fork
  • fork到底做了什么
  • 子进程继承了父进程哪些属性?
  • 父子进程有哪些不同?
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档