24.Linux 进程同步

在多道程序环境中,进程是并行执行的,父进程与子进程可能没有交集,各自独立执行;但也有可能,子进程的执行结果是父进程的下一步操作的先决条件,此时父进程就必须等待子进程的执行。

我们把异步环境下的一组并发进程因相互制约而相互发送消息、互相合作、互相等待,使得各进程按一定速度和顺序执行称为进程间的同步。

使用sleep()函数来控制进程的执行顺序,但这种方法只是一种权宜之计。系统中进程的执行顺序是由内核决定的,使用这种方法很难做到对进程的精确控制。

Linux系统中提供了wait()函数和waitpid()函数来获取进程状态,实现进程同步。

1.wait()函数存在系统库函数sys/wait.h中,函数声明如下:

pid_t wait(int *status);

调用wait()函数的进程会被挂起,进入·阻塞状态,直到子进程变为僵尸态,wait()函数捕获到该子进程的退出信息时才会转为运行态,回收子进程资源并返回。

没有变为僵尸态的子进程,wait函数会让进程一直阻塞。若当前进程有多个子进程,只要捕获到一个变为僵尸态的子进程的信息,wait()函数就会返回并使进程恢复执行。

函数中的参数status是一个int*类型的指针,它用来保存子进程退出时的状态信息。但通常,我们只想消灭僵尸进程,不在意子进程如何终止,因此一般将该参数设为NULL。

若wait()函数调用成功,wait()会返回子进程的进程id;若调用失败,wait()返回-1,errno被设为

置为ECHILD

案例6-4:若子进程p1是其父进程p的先决进程,使用wait()函数使进程同步。

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
        pid_t pid,w;
        pid = fork();
        if(pid == -1)
        {
                perror("fork error");
                exit(1);
        }

        else if(pid == 0)
        {
                sleep(3);
                printf("Child process : pid =%d\n",getpid());
        }
        else if(pid > 0)
        {
//若wait()函数调用成功,wait()会返回子进程的进程id
                w=wait(NULL);
                printf("Catched a child process,pid=%d\n",w);
        }
        return 0;
}
~          

执行结果如下:

 以上结果在执行程序3秒后输出,因为代码第13行使用sleep()函数使子进程沉睡3秒后才执行。观察程序的执行情况:子进程在程序执行3秒后完成并输出子进程pid;因为父进程中执行的操作只是回收子进程,所以父进程在子进程终止后立即输出。由执行情况可以知道,父进程在子进程结束后才结束,父进程成功捕获了子进程。

当然,wait()函数中的参数可以不为空。若status不为空,wait()函数会获取子进程的退出状态,退出状态被存放在exit()函数参数status的低八位。使用常规方法读取比较麻烦,因此Linux系统中定义了一组用于判断进程退出状态的宏函数,其中最基础的是WIFEXITED()和WEXITSTATUS(),它们

的参数与wait()函数相同,都是一个整型的status。宏函数的功能分别如下:

①WIFEXITED(status):用于判断子程序是否正常退出,若是,则返回非0值;否则返回0

②WEXITSTATUS(status):WEXITSTATUS()通常与WIFEXITED()结合使用,若WIFEXITED返回非0值(即正常退出),则使用该宏可以提取子进程的返回值。

案例6-5:使用wait()函数同步进程,并使用宏获取子进程的返回值。

#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
        int status;
        pid_t pid,w;
        pid = fork();
        if(pid == -1)
        {
                perror("fork error");
                exit(1);
        }
        else if(pid == 0)
        {
                sleep(3);
                printf("Child process:pid = %d\n ",getpid());
                exit(5);
        }
        else if(pid>0)
        {
                w=wait(&status);
                if(WIFEXITED(status))
                {
                        printf("Child process pid=%d exit normally.\n",w);
                        printf("Return Code:%d\n",WEXITSTATUS(status));
                }       
                else
                        printf("Child process pid =%d exit abnormally.\n",w);
        }
        return 0;
}       

执行结果如下:

 定义了一个整型变量status,该变量在wait()函数中获取子进程的退出码。之后通过宏WIFEXITED判断返回值是否为0,当不为0时,使用宏WEXITSTATUS将返回码转换成一个整数数据

2.waitpid()函数

wait()函数具有一定局限性,若当前进程有多个子进程,那么该函数就无法确保作为先决条件的子进程在父进程之前执行,此时可使用waitpid()函数实现进程同步。

waitpid()函数同样位于系统库函数sys/wait.h中,它的函数声明如下:

pid_t waitpid(pid_t pid,int *status,int options);

waitpid()函数比wait()函数多两个参数:pid 和options。

参数pid一般是进程的pid,但也会有其他取值。参数pid的取值及其意义分别如下:

①pid=-1时,waitpid()函数与wait()作用相同,将阻塞等待并回收一个子进程

②pid>0时,只等待pid与该参数相同的子进程,若该子进程退出,waitpid()函数就会返回;若

该子进程仍未结束,waitpid()函数就一直等待该进程。

③pid=0时,会等待同一个进程组的所有子进程,若子进程加入了其他进程组,waitpid()将不再关心它的状态。

④pid<-1时,会等待同一个进程组中的任何子进程,进程组的id等于pid的绝对值。

参数options提供控制waitpid()选项,该选项是一个常量或由 | 连接的两个常量。该参数支持的选项如下:

①WNOHANG。即使子进程没有终止,waitpid()也会立即返回,即不会使父进程阻塞。

②WUNTRACED.即使子进程暂停执行,则waitpid()立刻返回。

另外若不想使用该参数,可以将其值设置为0.

waitpid()函数的返回值会出现3种情况:

①正常返回时,waitpid()返回捕捉到的子进程的pid

②若options的值为WNOHANG,但调用waitpid()时发现没有已退出的子进程可收集,则返回0.

③若调用过程出错,则返回-1,errno会被设置成相应的值以指示错误位置。

waitpid()函数可以等待指定的子进程,也可以在父进程不阻塞的情况下获取子进程状态,相对于

wait函数来说,它的使用更为灵活。下面通过两个案例来学习waitpid()函数的用法。

案例6-6:使父进程等待进程组中某个指定的进程,若该进程不退出,则让父进程一直阻塞。

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
        pid_t pid,p,w;
        pid = fork();
        if(pid == -1)
        {
                perror("fork error");
                exit(1);
        }
        else if(pid == 0)
        {
                sleep(5);
                printf("First child process:pid=%d\n",getpid());
        }
        else if(pid > 0)
        {
                int i;
                p=pid;
                for(i=0;i<3;i++)
                {
                        if((pid = fork())==0)
                                break;
                }
                if(pid == -1)
                {
                        perror("fork error");
                        exit(2);
                }
                else if(pid == 0)
                {
                        printf("Child process : pid=%d\n",getpid());
                        exit(0);
                }
                else if(pid > 0)
                {
                        w=waitpid(p,NULL,0);//传入子进程的pid,第二个参数不设置,不使用第三个参数可设置为0
                        if(w==p)//正常返回时,waitpid()返回捕捉到的子进程的pid
                          printf("Catch a child process:pid=%d\n",w);
                        else
                          printf("waitpid error\n");            
                }
        }
        return 0;
}

执行结果如下:

 CPU执行速度极高,执行程序后可看到执行结果中的前3行会立刻被输出;结果中的第4行在5秒后输出,因为第13行代码要求第一次创建子进程时睡眠5秒;执行结果的第五行为父进程执行的操作,当第一个子进程终止后,此行立刻被输出。

案例6-7:使用waitpid()函数不断获取某进程中子进程的状态

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
        pid_t pid,w;
        pid = fork();
        if(pid == -1)
        {
                perror("fork error");
                exit(1);
        }       
        
        else if(pid == 0)
        {
                sleep(3);
                printf("Child process:pid =%d\n",getpid());
                exit(0);
        }
        
        else if(pid > 0)
        {
                do{
                        w=waitpid(pid,NULL,WNOHANG);
                        if(w==0)
                        {
                                printf("No child exited\n");                    
                                sleep(1);
                        }

                }while(w==0);
                if(w==pid)
                        printf("Catch a child process:pid=%d\n",w);
                else
                        printf("waitpid error\n");
        }
        return 0;
}       

执行结果如下:

 在父进程的代码段中设置了一个循环,在循环中调用waitpid()函数,并使用sleep()函数控制waitpid(),使其每隔1秒捕捉一次子进程信息;同时使子进程沉睡3秒,因此父进程会输出3次No 

child exited。3秒后子进程终止,waitpid()成功捕获到子进程的退出信息并使父进程继续运行,从而输出捕捉到的子进程id。

多学一招:

僵尸进程不能再次被运行,但是却会占据一定的内存空间:当系统中僵尸进程的数量很多时,不光会占用进程id。若僵尸进程一直存在,新的进程可能会因内存不足或一直无法获取pid而无法被创建。因此,应尽量避免僵尸进程的产生,使用wait()和waitpid()可以有效避免僵尸进程。

若僵尸进程已经产生,就应该想办法终止僵尸进程。通常情况下,解决僵尸进程的方法是终止其父进程。当僵尸进程的父进程被终止后,僵尸进程作为孤儿进程被init接收,init会不断调用wait()函数获取子进程状态,收集已退出的子进程发送的状态信息。孤儿进程永远不会成为僵尸进程。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值