进程间通信的竞争条件详解

来源:互联网 发布:淘宝文玩店推荐 编辑:程序博客网 时间:2024/05/17 07:28

一、竞争条件的含义

        竞争条件是由于协作的进程具有彼此都能够读写的共享存储区导致的,任何两个或多个进程读写某些共享数据,最后的结果取决于进程运行的时序,称为竞争条件。为了进一步了解竞争条件的含义,通过下面的两个案例来说明。

二、银行存取款

        假设你在银行有一个账户,账户余额为200元,在某一时刻,你决定去终端ATM取款,此时你登录ATM,读取到账户余额200元,然后点击取款功能,输入100元,此时由于你是按键操作,中间会有间隔,在你按下确定按键时,正好有另一个人正在给你网络转账,由于网络转账是通过发送转账信息到银行服务器,然后服务器更新你的存款余额,服务器读出你的存款余额也为200元,在这个时候你按下了取款按钮,然后取出100元,并且ATM之前读取到的账户余额200减去取出的100,账户余额应该为100元,然后ATM更新你的账户余额为100。而此时,另一个转账的进程也在更新你的账户余额,因为它之前读出的账户余额为200,现在加上转账进来的300,因此余额应该为500,然后更新你的账户余额为500。

        现在我们发现了,这两个操作所对应的进程,它们会给你的账户余额更新为不同的数目。如果进程A先更新你的账户余额,然后进程B再更新时,就会覆盖掉进程A更新的结果,此时你的账户余额就是500元。那么你就白白的赚了银行100元,如果更多的人发现这个漏洞,银行不就会跨了吗?哈哈!

       但是假如进程B先更新你的账户余额为500,然后进程A再更新你的账户余额为100,那么你最终的余额就是100,就会损失掉300!

       这种最后的结果依赖于进程运行的精确时序,就是竞争条件。所幸显示的银行系统意识到了这个问题,对存款余额这个变量设置为了临界区资源,同一时刻只允许一个进程访问,任何进程在访问存款余额时,都会给临界区上锁,直到它执行完,其他进程才能在来访问,因此现实之中,我们就不会出现上面的两种情况了。


三、假托机打印程序

          假脱机打印程序的工作过程是这样的:当一个进程需要打印某个文件时候,则将该文件名放在一个特殊的假脱机目录下。另一个进程(打印机守护程序)周期性的检查是否有文件需要打印,若有就打印并将该文件名从目录下删除掉。假设我们把假脱机的目录看做一个个的存储区域,每个区域对应一个地址,每个存储区域中放一个需要打印的文件名。假设有两个共享变量,分别指向下一个要打印的文件(out变量)和下一个空闲的存储区域(in变量),类似指针。在某个时候0到2号存储区域的文件已经打印完,下一个要打印的是3号存储区域的文件,并且当前目录中等待打印的文件有3个,因此下一个空闲的存储区域是6号。则out指向3,in指向6。如下图所示


      现在有两个进程到来,进程A需要打印文件1.txt,进程B需要打印文件2.txt。它们几乎是同时到来,因此它们读取in变量,得到空闲的存储区域为6。于是进程A和进程B均将6存储到自己的局部变量中,然后两个进程继续运行。接着这两个进程就要把打印的文件名写入到打印机的目录中,因为它们都以为空闲的存储区域是6,所以进程A和进程B都会向6号存储区域写入待打印的文件名。如果进程A先更新6号存储区域,然后更新in的值为 7,那么当进程B更新6号存储区域时,就会覆盖掉先写入的1.txt,结果目录中就只有2.txt成功的写入目录。反之,如果进程B先执行写入,则最后1.txt文件名就会覆盖掉2.txt。总之两个打印任务有一个一定会被丢失掉,永远也不会被打印。而打印机的目录和相应的in和out变量都是一致的,打印机守护程序是发现不了任何错误。因此在这个案例中,由于多个进程访问同一个共享资源出现了竞争条件。

       下面我通过两个简单的多线程程序模拟上述两个案例,具体的展现竞争条件。

四、程序代码与结果

      该部分的实际运行结果都出现了两种不同结果,这就是竞争条件所言的,最终结果是与进程实际运行的精确时序有关。

银行存款案例代码:

#include "stdio.h"
#include "pthread.h"
#include "windows.h"
#pragma comment(lib, "pthreadVC2.lib")
int account_balance=200;
void *draw(void *ptr)
{
int k=0;
k=account_balance-100;
printf("取款100.\n");
Sleep(100);
account_balance=k;
pthread_exit(0);
return NULL;
}
void *deposit(void *ptr)
{
int k=0;
k=account_balance+300;
printf("存款300.\n");
Sleep(100);
account_balance=k;
pthread_exit(0);
return NULL;
}
int main(int argc,char **argv)
{
pthread_t dr,de;
pthread_create(&dr,0,draw,0);
pthread_create(&de,0,deposit,0);
pthread_join(dr,0);
pthread_join(de,0);
printf("账户余额:%d.\n",account_balance);
return 0;
}

运行结果


假脱机打印程序案例代码

#include "stdio.h"
#include "pthread.h"
#include "stdlib.h"
#include "windows.h"
#pragma comment(lib, "pthreadVC2.lib")
char **catalog;
int input=0,output=0;
void *printA(void *ptr)
{
int k=0;
k=input;
Sleep(100);
catalog[input]="1.txt";
input=k+1;
pthread_exit(0);
return NULL;
}
void *printB(void *ptr)
{
int k=0;
k=input;
Sleep(100);
catalog[input]="2.txt";
input=k+1;
pthread_exit(0);
return NULL;
}
int main(int argc,char **argv)
{
pthread_t A,B;
int i=0;
catalog=(char **)malloc(10*sizeof(char *));
for (i=0;i<19;i++)
{
catalog[i]=(char *)malloc(20*sizeof(char));
}
catalog[3]="A.doc";
catalog[4]="B.txt";
catalog[5]="C.doc";
output=3;
input=6;
printf("进程A和进程B到来前的打印机目录:\n");
for (i=output;i<input;i++)
{
printf("%s\n",catalog[i]);
}
pthread_create(&A,0,printA,0);
pthread_create(&B,0,printB,0);
pthread_join(A,0);
pthread_join(B,0);
printf("\n进程A和进程B写入文件名后的打印机目录:\n");
for (i=output;i<input;i++)
{
printf("%s\n",catalog[i]);
}
return 0;
}

运行结果




0 0