• 多线程学习(C/C++)


    1.进程

            运行着的程序就是进程

            进程的特性:1.独立性 2.动态性 3.并发性

            (1)进程的状态

            进程一共有五种状态分别为:创建态,就绪态,运行态,阻塞态(挂起态),退出态(终止态)其中创建态和退出态维持的时间是非常短的,稍纵即逝。我们主要是需要将就绪态, 运行态, 挂起态,三者之间的状态切换搞明白        

            就绪态: 万事俱备,只欠东风(CPU资源)

    • 进程被创建出来了,有运行的资格但是还没有运行,需要抢CPU时间片
    • 得到CPU时间片,进程开始运行,从就绪态转换为运行态。
    • 进程的CPU时间片用完了, 再次失去CPU, 从运行态转换为就绪态。


            运行态:获取到CPU资源的进程,进程只有在这种状态下才能运行

    • 运行态不会一直持续,进程的CPU时间片用完之后, 再次失去CPU,从运行态转换为就绪态
    • 只要进程还没有退出,就会在就绪态和运行态之间不停的切换。

            阻塞态:进程被强制放弃CPU,并且没有抢夺CPU时间片的资格

    • 比如: 在程序中调用了某些函数(比如: sleep()),进程又运行态转换为阻塞态(挂起态)
    • 当某些条件被满足了(比如:slee() 睡醒了),进程的阻塞状态也就被解除了,进程从阻塞态转换为就绪态。

             退出态: 进程被销毁, 占用的系统资源被释放了

    • 任何状态的进程都可以直接转换为退出态。

            创建子进程的过程就是cow(copy-on-write)写时拷贝

            写时拷贝(COW):copy-on-write:多个调用者指向同一块地址空间,如果其中有一个要对地址空间的内容进行修改的话,就会拷贝一个内容、大小和原来地址空间一模一样的地址,然后再让要修改的变量指向拷贝的那一块地址空间,然后再进行修改。写时拷贝的优点:减少资源的占用,缺点:只是将资源的占用延后了

            (2)特殊进程
                    1.僵尸进程             

            在一个启动的进程中创建子进程,这时候就有了父子两个进程,父进程正常运行, 子进程先与父进程结束, 子进程无法释放自己的PCB资源, 需要父进程来做这个件事儿, 但是如果父进程也不管, 这时候子进程就变成了僵尸进程。

                    2.孤儿进程

            在一个启动的进程中创建子进程,这时候父子进程同时运行,但是父进程由于某种原因先退出了,子进程还在运行,这时候这个子进程就可以被称之为孤儿进程(跟现实是一样的)。

                    3.守护进程

            守护进程(daemon)是一类在后台运行的特殊进程,用于执行特定的系统任务。很多守护进程在系统引导的时候启动,并且一直运行直到系统关闭。另一些只在需要的时候才启动,完成任务后就自动结束。(守护进程是一种特殊的孤儿进程)

    1.Linux下查看系统进程信息的命令

    ps aux
        - a: 查看所有终端的信息
        - u: 查看用户相关的信息
        - x: 显示和终端无关的进程信息

    ps ajx

         - a: 查看所有终端的信息

         -j: 采用工作控制的格式显示进程状况

        - x: 显示和终端无关的进程信息

    2.进程函数操作中使用的一些常用函数    

    (1)创建子进程函数fork、vfork

    #include

    #include

    pid_t fork(void);

    功能:

                在已有的进程基础上又创建一个子进程

    参数:

               

    返回值:

                 成功:

                      >0 子进程的进程号,标识父进程的代码区

                      =0 子进程的代码区

                 失败:

                      -1 返回给父进程,子进程不会创建

    #include

    #include

    pid_t vfork(void);

    功能:

                在已有的进程基础上又创建一个子进程

    参数:

               

    返回值:

                 成功:

                      >0 子进程的进程号,标识父进程的代码区

                      =0 子进程的代码区

                 失败:

                      -1 返回给父进程,子进程不会创建

            

    [1]fork与vfork的区别:

    1)fork(): 子进程拷贝父进程的堆栈段、数据段,代码段

         vfork(): 子进程与父进程共享资源

    2)fork(): 父子进程执行次序不确定

         vfork(): 保证子进程先运行,在调用exec()或exit()之前,与父进程数据共享,在exec()或exit()调用之后,父进程才能运行

    (2)获取进程号函数 :getpid、getppid
    #include
    #include
    pid_t getpid(void);
    功能:
         用来取得执行目前进程的pid号
    参数:
         
    返回值:
          目前进程的pid号
    

    #include

    #include

    pid_t getppid(void);

    功能:

            用来取得执行目前进程的父进程的pid号

    参数:     

            无

    返回值:      

            当前进程的父进程的pid号

    1. #include
    2. #include
    3. #include
    4. int main(){
    5. int num = 10;
    6. pid_t pid = fork();
    7. //pid_t pid = vfork();
    8. if(pid > 0){
    9. num += 10;
    10. printf("in parent child pid %d, %d\n",pid, num);
    11. exit(0); //退出进程
    12. }else if(0 == pid){
    13. num += 5;
    14. printf("in child pid %d, num %d\n",getpid(), num);
    15. exit(0); //退出进程
    16. }else{
    17. printf("failed\n");
    18. }
    19. return 0;
    20. }

    运行结果:

     (3)进程回收函数wait、waitpid

            为了避免僵尸进程的产生,一般我们会在父进程中进行子进程的资源回收,回收方式有两种,一种是阻塞方式wait(),一种是非阻塞方式waitpid()

     3.进程间通信的方法

    进程间的通信方式有:(一共7种方式)
    1.管道:无名管道、有名管道
    2.信号:信号 信号量
    3.消息队列
    4.共享内存
    5.套接字

    (1)进程间通信——无名管道pipe
             1.创建无名管道函数pipe

    #include

    #include

    int pipe(int pipefd[2])

    作用:创建一个匿名管道,用来进程间通信;

    参数:

            int pipefd[2]这个数组是一个传出参数;

                    pipefd[0] 对应管道的读端;

                    pipefd[1] 对应管道的写端;

    返回值:

            成功 0;

            失败-1;

    1. #include
    2. #include
    3. #include
    4. int main(){
    5. int fd[2] = {-1, -1}; //fd[0]是读端 fd[1]是写端
    6. int res = pipe(fd);
    7. if(res == -1){
    8. perror("create pipe failed");
    9. return -1;
    10. }
    11. pid_t pid = fork();
    12. if(pid > 0){
    13. wait(NULL); //等待子进程退出
    14. printf("in parent\n");
    15. char buf[100] = {0};
    16. read(fd[0], buf,100);
    17. printf("recv %s",buf);
    18. exit(0);
    19. }else if(pid == 0){
    20. printf("in child\n");
    21. write(fd[1], "hello", 5);
    22. exit(0); //系统退出,退出进程
    23. }
    24. return 0; //函数的返回
    25. }

     运行结果:

    (2)进程间通信——有名管道mkfifo
            1.创建无名管道函数(mkfifo——创建管道文件)

    #include
    #include

    int mkfifo(const char *filename,mode_t mode);

    功能:

            创建一个管道文件

    参数:

            filename:管道文件名

            mode:管道文件权限

                   O_RDONLY:读管道

        O_WRONLY:写管道

        O_RDWR:读写管道

        O_NONBLOCK:非阻塞

    返回值:

            若函数成功执行则返回值为0

            失败返回-1

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. int main(){
    8. int ret = mkfifo("myfifo",0666); //创建管道文件
    9. if(ret == -1){
    10. perror("mkfifo");
    11. return -1;
    12. }
    13. int fd = open("myfifo", O_RDWR);
    14. pid_t pid = fork();
    15. if(pid > 0){
    16. wait(NULL);
    17. printf("in parent\n");
    18. char buf[100] = {0};
    19. read(fd, buf,100);
    20. printf("recv %s",buf);
    21. exit(0);
    22. }else if(pid == 0){
    23. printf("in child\n");
    24. write(fd, "hello", 5);
    25. exit(0); //系统退出,退出进程
    26. }
    27. return 0; //函数的返回
    28. }

    运行结果:

     

    (3)进程间通信——信号signal

    Linux中的信号:可以用kill -l命令查看 

    #include

    typedef void(*sighandler_t)(int);

    sighandler_t signal(int signum, sighandler_t handler);

    功能:

            设置某个信号的捕捉行为
    参数:
                signum:要捕捉的信号
                handler:捕捉到信号要如何处理
                            SIG_IGN:忽略信号
                            SIG_DFL:使用信号默认的行为

     回调函数:

                    这个函数是内核调用,程序员只负责写,捕捉到信号后如何去处理信号。
     回调函数:
                 需要程序员实现,提前准备好的,函数的类型根据实际需求,看函数指针的定义不是程序员调用,而是当信号产生,由内核调用函数指针是实现回调的手段,函数实现之后,将函数名放到函数指针的位置

    返回值:
                成功返回上一次注册的信号处理函数的地址。第一次调用返回NULL
                失败返回SIG_ERR,设置错误号
                9)SIGKILL 和 19)SIGSTOP 不能被捕捉和忽略

    #include

    #include

    int kill(pid_t pid, int sig);

    功能:

            发送一个信号给进程号为pid的进程

    参数:

            pid:进程的id

            sig:要发送信号

    返回值:

            成功返回0

            错误返回-1

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. void sighandle(int signum){
    7. printf("signum is %d\n",signum);
    8. }
    9. int main(){
    10. pid_t pid = fork();
    11. if(pid > 0){
    12. //signal(SIGQUIT,SIG_IGN); //SIG_IGN忽视信号
    13. //signal(SIGQUIT,SIG_DFL); //SIG_DFL默认的方式去处理
    14. signal(SIGQUIT,sighandle); //自定义处理
    15. wait(NULL);
    16. printf("in parent\n");
    17. exit(0);
    18. }else if(pid == 0){
    19. printf("in child\n");
    20. kill(getppid(),3);//SIGQUIT == 3 //getppid()获取进程的父进程pid号
    21. exit(0); //系统退出,退出进程
    22. }
    23. return 0; //函数的返回
    24. }

    运行结果:

     

    (4)消息队列——msgget、msgrcv、msgsnd

            在Linux中查看消息队列命令:ipcs -q 

    #include

    #include

    #include

    int msgget(key_t key, int msgflg);

    功能:

            创建或打开消息队列 

    参数:

             key:和消息队列关联的key值

            msgflg:

                    0:取消息队列标识符,若不存在则函数会报错

                    IPC_CREAT:当msgflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的消息队列,则新建一个消息队列;如果存在这样的消息队列,返回此消息队列的标识符

                    IPC_CREAT|IPC_EXCL:如果内核中不存在

  • 相关阅读:
    Jmeter post请求传参问题
    builder(建造者模式)
    LeetCode 136. 只出现一次的数字
    为啥不建议使用Select *
    海外众筹如何通过邮件营销?
    PMP新考纲通关秘籍,告别抓瞎
    GoF23—抽象工厂模式
    Node.js --- 前端高薪之路绕不过去的坎
    Spring Cloud Ribbon:负载均衡的服务调用
    基于协同过滤算法的图书推荐系统设计与实现
  • 原文地址:https://blog.csdn.net/weixin_44954230/article/details/133315499