百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

Linux-多进程开发,看完就明白了

mhr18 2025-05-14 14:56 6 浏览 0 评论

  /*
  #include <sys/types.h>
  #include <unistd.h>
  pid_t fork(void);
  作用:创建子进程
      返回值:
          fork()返回值会返回两次,一次是在父进程中,一次是在子进程中
          在父进程中返回创建的子进程的ID,
          在子进程中,返回0
          如何区分父进程和子进程,通过fork的返回值
          在父进程中返回-1,表示创建子进程失败,并且设置errno


      父子进程之间的关系:
      区别:
          1.fork()函数的返回值不同
              父进程中:>0,返回子进程的PID
              子进程中:=0
          2.pcb中的一些数据
              当前进程的id pid
              当前进程的父进程的id ppid
              信号集
          共同点:
              某些状态下,子进程刚被创建出来,还没有执行任何的写数据操作
                  -用户区的数据
                  -文件描述符表
              父子进程对变量是否是共享的
                  -刚开始是一样的,如果修改类数据,就不共享了
                  读时共享(子进程刚创建),写时拷贝
                  父子进程之间不能用变量进行通信,它们之间互不影响

  */
  #include <sys/types.h>
  #include <unistd.h>
  #include <stdio.h>
  int main()
  {

      int num =10;
      //创建子进程
      pid_t pid = fork();//pid_t本质上是int类型的
      //判断是父进程还是子进程
      if(pid>0){
          printf("pid : %d\n", pid);
          //如果大于0,返回的是创建的子进程的id,当前是父进程
          printf("i am parent process,pid : %d, ppid:%d\n",getpid(),getppid());
          printf("parent num: %d\n",num);
          num+=10;
          printf("parent num+=10: %d\n",num);
      }else if(pid ==  0)
      {
          //当前是子进程
          printf("i am child process, pid :%d,ppid : %d\n",getpid(),getppid());
          printf("child num: %d\n",num);
          num+=100;
          printf("child num+=100: %d\n",num);
      }
      for(int i =0;i<3;i++)//默认父子进程都会去执行
      {
          printf("i : %d\n", i);
          sleep(1);

      }
      return 0;
  }

exec函数族

可执行文件的用户区去替换进程中的用户区,一般情况下会创建一个子进程,在子进程创建exec函数族中的函数

查看execl

execlp

/*#include <unistd.h>

       extern char **environ;

       int execlp(const char *file, const char *arg, ...);
        -会到环境变量中查找指定的可执行文件,如果找到了就执行,找不到就
            参数:-file:需要指定的执行的可执行文件的文件名
                a.out    ps
                  -arg:是执行可执行文件所需要的参数列表
                    第一个参数一般没有什么作用,为了方便一般写的执行的参数的名称,从第二个人参数开始往后,就是程序执行所需要的参数列表
                    参数最后需要以NULL结束(哨兵)
             返回值:-仅仅在出错的时候返回,返回-1,并且设置errno
             如果调用成功,就没有返回值
                    
*/
#include <unistd.h>
#include <stdio.h>

int main()
{
    //创建一个子进程在子进程中执行exec函数族中的函数

    pid_t pid =fork();
    if(pid >0){
        //父进程
        printf("i am parent process,pid : %d\n",getpid());
        sleep(1);
    }else if(pid == 0)
    {
        //子进程
        execlp("ps","ps","aux",NULL);
        printf("i am child process , pid:%d\n",getpid());
    }
    for(int i =0;i<3;i++){
        printf("i=%d , pid = %d\n",i,getpid());
    }

    return 0;
}

可执行程序的程序名,第二个参数

进程控制

fork函数 读时共享,写时复制 主要目的是降低内存的使用

子进程退出,父进程能得到子进程退出的状态 父进程有义务回收子进程的资源

#include <stdlib.h>
#include<unistd.h>
#include <stdio.h>
int main()
{
    printf("hello\n");
    printf("world");

    exit(0);

    return 0;  //和exit是一样的,都表示进程退出

}

如下,1领养了孤儿进程 ,由1进程回收资源

将exit()改成_exit()后 world在缓冲区里

孤儿进程

父进程运行结束,子进程还在运行,这样的子进程称为孤儿进程

每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为 init ,而 init进程会循环地 wait() 它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init 进程就会代表出面处理它的一切善后工作。

孤儿进程并不会有什么危害

如下,1领养了孤儿进程 ,由1进程回收资源

父进程结束后,显示了一个终端(回到前台)

为什么都显示在终端?父子进程的内核去的一些部分是共享的 文件描述符表前三个(0,1,2)标准输入,输出,错误

关于C/C++Linux后台服务器开发高级架构师知识点学习视频 点击 正在跳转 获取,内容知识点包括Linux,Nginx,ZeroMQ,MySQL,Redis,线程池,MongoDB,ZK,Linux内核,CDN,P2P,epoll,Docker,TCP/IP,协程,DPDK等等。

僵尸进程

每个进程结束之后, 都会释放自己地址空间中的用户区数据,内核区的 PCB 没有办法自己释放掉,需要父进程去释放。
进程终止时,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸
(Zombie)进程。

僵尸进程不能被kii -9杀死

僵尸进程 内核区数据没办法释放

如何解决呢?

ctrl+c 发送一个9号信号把进程杀死 杀死父进程

此时僵尸进程就不存在了 不过此方法只是在演示阶段使用,一般是wait 或waitpid

wait

#include <sys/types.h>
#include <sys/wait.h>
#include<stdio.h>
#include<unistd.h>
#include <stdlib.h>
int main()
{
    //有一个父进程,创建5个子进程(兄弟)
    pid_t pid;
    for(int i=0;i<5;i++)    //这里不只产生5个
    {
        pid =fork();
        if(pid ==0)
        {
            break;       //此时子进程就不会产生孙子进程
        }
    }
    if(pid >0)
    {//父进程
        while(1)
            {
            printf("i am parent,pid = %d\n",getpid());
            //int ret = wait(NULL);
            int st;
            int ret = wait(&st);
            if(ret ==-1)
            {
                break;
            }

            if(WIFEXITED(st))
            {
                //是不是正常退出
                printf("退出的状态码:%d\n",WEXITSTATUS(st));
            }
            if(WIFSIGNALED(st))
            {
                //是不是异常终止
                printf("被哪个信号干掉了:%d\n",WTERMSIG(st));
            }
            printf("child die,pid =%d\n",ret);  //如果成功了,返回值就是子进程的id,失败返回-1
            sleep(1);
            }
    }
    else if (pid ==0){
        //子进程
        while(1)
        {
            printf("child,pid = %d\n",getpid());
            sleep(1);

        }
        exit(1);




    }
    return 0;

}

通过信号杀死

kill -9

默认是阻塞状态

非阻塞状态

int main()
{
    //有一个父进程,创建5个子进程(兄弟)
    pid_t pid;
    for(int i=0;i<5;i++)    //这里不只产生5个
    {
        pid =fork();
        if(pid ==0)
        {
            break;       //此时子进程就不会产生孙子进程
        }
    }
    if(pid >0)
    {//父进程
        while(1)
            {
            printf("i am parent,pid = %d\n",getpid());
            sleep(1);
            //int ret = wait(NULL);
            int st;
            int ret = waitpid(-1,&st,WNOHANG);//非阻塞
            if(ret ==-1)
            {
                break;
            }
            else if(ret == 0)
            {
                //还有子进程存在
                continue;
            }else if(ret>0)//回收到某一个具体的子进程
            {

                if(WIFEXITED(st))
                {
                    //是不是正常退出
                    printf("退出的状态码:%d\n",WEXITSTATUS(st));
                }
                if(WIFSIGNALED(st))
                {
                    //是不是异常终止
                    printf("被哪个信号干掉了:%d\n",WTERMSIG(st));
                }
                printf("child die,pid =%d\n",ret);  //如果成功了,返回值就是子进程的id,失败返回-1
            }
            sleep(1);
            }
    }
    else if (pid ==0){
        //子进程
        while(1)
        {
            printf("child,pid = %d\n",getpid());
            sleep(1);
        }
        exit(1);
    }
    return 0;
}

父进程可继续往下执行

进程间通信

进程是一个独立的资源分配单元,不同进程(这里所说的进程通常指的是用户进程)之间的资源是独立的,没有关联,不能在一个进程中直接访问另一个进程的资源。

进程不是孤立的,不同的进程需要进行信息的交互和状态的传递等,因此需要进程间通信(IPC)

03匿名管道

管道也叫无名(匿名)管道,它是是 UNIX 系统 IPC(进程间通信)的最古老形式,所有的 UNIX 系统都支持这种通信机制。

统计一个目录中文件的数目命令:ls | wc –l,为了执行该命令,shell 创建了两个进程来分别执行 ls 和 wc。

管道两端对应两个文件描述符支持读操作和写操作

一个管道是一个字节流,使用管道时不存在消息或者消息边界的概念,从管道读取数据的进程可以读取任意大小的数据块,而不管写入进程写入管道的数据块的大小是多少

通过管道传递的数据是顺序的,从管道中读取出来的字节的顺序和它们被写入管道的顺序是完全一样的。

在管道中的数据的传递方向是单向的,一端用于写入,一端用于读取,管道是半双工的。 全双工:

半双工:就像对讲机 需要CSMA/CD

从管道读数据是一次性操作,数据一旦被读走,它就从管道中被抛弃,释放空间以便写更多的数据,在管道中无法使用 lseek() 来随机的访问数据。

匿名管道只能在具有公共祖先的进程(父进程与子进程,或者两个兄弟进程,具有亲缘关系)之间使用。

管道数据结构

循环队列

示例:

/*
 #include <unistd.h>

       int pipe(int pipefd[2]);

    功能:创建一个匿名管道,用来进程间通信
    参数:int pipefd[2]这个数组是一个传出参数
        Pipefd[0]对应的是管道的读端
        pipefd[1]对应的是管道的写端
    返回值: 成功返回0,失败返回-1
    注意:匿名管道只能用于具有关系的进程间的通信(父子,兄弟)
*/
//子进程发送数据给父进程,父进程读取到数据输出
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include<string.h>
int main()
{
    //在fork()之前创建管道
    int pipefd[2];

    int ret = pipe(pipefd);
    if(ret == -1)
    {
        perror("pipe");
        exit(0);
    }
    //创建子进程
    pid_t pid = fork();
    if(pid>0)
    {
        //父进程
        //从管道的读取段读取数据
        char buf[1024]={0};
        int len = read(pipefd[0],buf,sizeof(buf));
        printf("parent recv : %s,pid : %d\n",buf,getpid());
    }
    else if(pid == 0)
    {
        sleep(10);
        //子进程
        char* str = "hello,i am child";
        write(pipefd[1],str,strlen(str));
    }


    return 0;
}

只有当管道里面有数据,才能去读,不然处于

父子进程相互读写

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include<string.h>
int main()
{
    //在fork()之前创建管道
    int pipefd[2];

    int ret = pipe(pipefd);
    if(ret == -1)
    {
        perror("pipe");
        exit(0);
    }
    //创建子进程
    pid_t pid = fork();
    if(pid>0)
    {
        printf("i am parent process pid : %d\n",getpid());
        //父进程
        //从管道的读取段读取数据
        char buf[1024]={0};
        while(1)
        {
        int len = read(pipefd[0],buf,sizeof(buf));
        printf("parent recv : %s,pid : %d\n",buf,getpid());


        char* str = "hello,i am parent";
        write(pipefd[1],str,strlen(str));
        sleep(1);
        }

    }
    else if(pid == 0)
    {
        sleep(10);
        //子进程
    printf("i am child process,pid : %d\n",getpid());
     char buf[1024]={0};
        while(1)
        {
        //向管道中写入数据
        char* str = "hello,i am child";
        write(pipefd[1],str,strlen(str));
        sleep(1);

        int len = read(pipefd[0],buf,sizeof(buf));
        printf("child recv : %s,pid : %d\n",buf,getpid());
        }

    }


    return 0;
}

ulimit -a 管道大小:8块 512字节/块 4k

存在t的原因,是没有清除数组

为什么会出现这种情况?

自己写入的数据被自己读了

怎么解决这个问题? 父子进程要么读,要么写,一般情况下不会出现一个子进程又读又写

管道非阻塞

char buf[1024]={0};
        int flags = fcntl(pipefd[0],F_GETFL);//获取原来的flag标记
        flags |= O_NONBLOCK;    //修改flag的值
        fcntl(pipefd[0],F_SETFL,flags);  //设置新的flag

有名管道

FIFO 提供了一个路径名与之关联 其打开方式与打开一个普通文件是一样的 只要可以访问该路径,就能够彼此通过FIFO通信

有名管道的使用

mkfifo 名字 严格遵循先进先出

方法1: 通过命令创建

方法2:

通过函数创建

int main()
{

    int ret = mkfifo("fifo1",0664);
    if(ret == -1)
    {
        perror("mkfifo");
        exit(0);
    }
    return 0;
}

相关推荐

B站收藏视频失效?mybili 收藏夹备份神器完整部署指南

本内容来源于@什么值得买APP,观点仅代表作者本人|作者:羊刀仙很多B站用户都有过类似经历:自己精心收藏的视频突然“消失”,点开一看不是“已被删除”,就是“因UP主设置不可见”。而B站并不会主动通知...

中间件推荐初始化配置

Redis推荐初始化配置bind0.0.0.0protected-modeyesport6379tcp-backlog511timeout300tcp-keepalive300...

Redis中缓存穿透问题与解决方法

缓存穿透问题概述在Redis作为缓存使用时,缓存穿透是常见问题。正常查询流程是先从Redis缓存获取数据,若有则直接使用;若没有则去数据库查询,查到后存入缓存。但当请求的数据在缓存和数据库中都...

后端开发必看!Redis 哨兵机制如何保障系统高可用?

你是否曾在项目中遇到过Redis主服务器突然宕机,导致整个业务系统出现数据读取异常、响应延迟甚至服务中断的情况?面对这样的突发状况,作为互联网大厂的后端开发人员,如何快速恢复服务、保障系统的高可用...

Redis合集-大Key处理建议

以下是Redis大Key问题的全流程解决方案,涵盖检测、处理、优化及预防策略,结合代码示例和最佳实践:一、大Key的定义与风险1.大Key判定标准数据类型大Key阈值风险场景S...

深入解析跳跃表:Redis里的&quot;老六&quot;数据结构,专治各种不服

大家好,我是你们的码农段子手,今天要给大家讲一个Redis世界里最会"跳科目三"的数据结构——跳跃表(SkipList)。这货表面上是个青铜,实际上是个王者,连红黑树见了都要喊声大哥。...

Redis 中 AOF 持久化技术原理全解析,看完你就懂了!

你在使用Redis的过程中,有没有担心过数据丢失的问题?尤其是在服务器突然宕机、意外断电等情况发生时,那些还没来得及持久化的数据,是不是让你夜不能寐?别担心,Redis的AOF持久化技术就是...

Redis合集-必备的几款运维工具

Redis在应用Redis时,经常会面临的运维工作,包括Redis的运行状态监控,数据迁移,主从集群、切片集群的部署和运维。接下来,从这三个方面,介绍一些工具。先来学习下监控Redis实时...

别再纠结线程池大小 + 线程数量了,没有固定公式的!

我们在百度上能很轻易地搜索到以下线程池设置大小的理论:在一台服务器上我们按照以下设置CPU密集型的程序-核心数+1I/O密集型的程序-核心数*2你不会真的按照这个理论来设置线程池的...

网络编程—IO多路复用详解

假如你想了解IO多路复用,那本文或许可以帮助你本文的最大目的就是想要把select、epoll在执行过程中干了什么叙述出来,所以具体的代码不会涉及,毕竟不同语言的接口有所区别。基础知识IO多路复用涉及...

5分钟学会C/C++多线程编程进程和线程

前言对线程有基本的理解简单的C++面向过程编程能力创造单个简单的线程。创造单个带参数的线程。如何等待线程结束。创造多个线程,并使用互斥量来防止资源抢占。会使用之后,直接跳到“汇总”,复制模板来用就行...

尽情阅读,技术进阶,详解mmap的原理

1.一句话概括mmapmmap的作用,在应用这一层,是让你把文件的某一段,当作内存一样来访问。将文件映射到物理内存,将进程虚拟空间映射到那块内存。这样,进程不仅能像访问内存一样读写文件,多个进程...

C++11多线程知识点总结

一、多线程的基本概念1、进程与线程的区别和联系进程:进程是一个动态的过程,是一个活动的实体。简单来说,一个应用程序的运行就可以被看做是一个进程;线程:是运行中的实际的任务执行者。可以说,进程中包含了多...

微服务高可用的2个关键技巧,你一定用得上

概述上一篇文章讲了一个朋友公司使用SpringCloud架构遇到问题的一个真实案例,虽然不是什么大的技术问题,但如果对一些东西理解的不深刻,还真会犯一些错误。这篇文章我们来聊聊在微服务架构中,到底如...

Java线程间如何共享与传递数据

1、背景在日常SpringBoot应用或者Java应用开发中,使用多线程编程有很多好处,比如可以同时处理多个任务,提高程序的并发性;可以充分利用计算机的多核处理器,使得程序能够更好地利用计算机的资源,...

取消回复欢迎 发表评论: