操作系统临界区问题
一、实验目的
本实验讨论临界区问题及其解决方案。实验首先创建两个共享数据资源的并
发线程。在没有同步控制机制的情况下,我们将看到某些异常现象。
针对观察到的现象,本实验采用两套解决方案:
• 利用Windows 的mutex 机制
• 采用软件方案
然后比较这两种方案的性能优劣。
二、实验步骤
1、制造混乱
Windows 操作系统支持抢先式调度,这意味着一线程运行一段时间后,操作
系统会暂停其运行并启动另一线程。也就是说,进程内的所有线程会以不可预知
的步调并发执行。
为了制造混乱,我们首先创建两个线程t1 和t2。父线程(主线程)定义两
个全局变量,比如accnt1 和accnt2。每个变量表示一个银行账户,其值表示该
账户的存款余额,初始值为0。线程模拟在两个账户之间进行转账的交易。也即,
每个线程首先读取两个账户的余额,然后产生一个随机数r,在其中一个账户上
减去该数,在另一个账户上加上该数。代码如下:
#include
#include
#include
int accnt1=0;
int accnt2=0;
double begin=0;
double end=0;
double time=0;
int a=1;
//HANDLE hMutex=CreateMutex(NULL,FALSE,NULL);
DWORD WINAPI run(LPVOID p) {
int counter=0;
int tmp1,tmp2,r;
//WaitForSingleObject(hMutex,INFINITE);
begin=GetTickCount();
do {
tmp1=accnt1;
tmp2=accnt2;
r=rand();
accnt1=tmp1+r;
accnt2=tmp2-r;
counter++;
} while (accnt1+accnt2==0&&counter<1000000);
end=GetTickCount();
time=end-begin;
printf("进?程¨¬%d所¨´用®?时º¡À间?为a%lf\n",a,time);
a++;
//ReleaseMutex(hMutex);
counter=0;
return 0;
}
int main(int argc, char *argv[])
{
CreateThread(NULL,0,run,NULL,0,NULL);
CreateThread(NULL,0,run,NULL,0,NULL);
system("PAUSE");
return 0;
}
执行结果如下:
从运行结果可以看出,每次进程2都可以全部完成,完成一次计数所需要的时间
为62ms,而进城1无法全部完成,所用时间不一定。
混乱制造成功。
2、解决方案
方案一:mutex 方案
Windows 操作系统提供了mutex 对象。mutex 状态可以signaled(unlocked)
或者是nonsignaled (locked)。利用mutex 对象,可以方便地实现临界区保护。
进入临界区时(在第一个读操作之前),锁住mutex 对象;离开临界区时(在第
二个写操作之后),打开mutex 对象。线程的阻塞与唤醒由系统管理,程序员无
需干预。以下给出的是在Windows 操作系统下有关mutex 对象操作的提示。创建
一个未上锁mutex 对象的代码如下:
#include
#include
#include
int accnt1=0;
int accnt2=0;
double begin=0;
double end=0;
double time=0;
int a=1;
HANDLE hMutex=CreateMutex(NULL,FALSE,NULL);
DWORD WINAPI run(LPVOID p) {
int counter=0;
int tmp1,tmp2,r;
WaitForSingleObject(hMutex,INFINITE);
begin=GetTickCount();
do {
tmp1=accnt1;
tmp2=accnt2;
r=rand();
accnt1=tmp1+r;
accnt2=tmp2-r;
counter++;
} while (accnt1+accnt2==0&&counter<1000000);
end=GetTickCount();
time=end-begin;
printf("进?程¨¬%d所¨´用®?时º¡À间?为a%lf\n",a,time);
a++;
ReleaseMutex(hMutex);
counter=0;
return 0;
}
int main(int argc, char *argv[])
{
CreateThread(NULL,0,run,NULL,0,NULL);
CreateThread(NULL,0,run,NULL,0,NULL);
system("PAUSE");
return 0;
}
运行结果如下:
从运行结果可以看出,利用mutex 方案并测定进城运行时间可以看出,两个进程
运行时间大体相同,进程运行完全,从而可以认为mutex方案有效的防止了两个
进程的相互交叉和阻碍。
方案二:软件方案
现在假设操作系统没有提供同步原语。这时,我们只能通过编程语言
对变量的操作实现临界区保护。下面给出的是一个概念性的解决方案框
架:
1 /_ CS Algorithm: Peterson Solution _/
2 int c1 = 0, c2 = 0, will wait ;
3 cobegin
4 p1: while (1) {
5 c1 = 1;
6 will wait = 1;
7 while( c2 && (will wait==1) ); /_wait loop_/
8 CS1;
9 c1 = 0;
10 program1;
11 }
12 p2: while (1) {
13 c2 = 1;
14 will wait = 2;
15 while( c1 && (will wait==2) ); /_wait loop_/
16 CS2;
17 c2 = 0;
18 program2;
19 }
上面的方案使用了三个变量c1; c2 和will wait。线程i 试图进入临界
区时首先把变量ci 置为1,接着把变量will wait 的值设置为i(为什么?)
阻塞通过临界区之前的循环实现。当线程退出临界区时,又把变量ci 的值
设置为0。
代码如下:
#include
#include
#include
int accnt1=0,accnt2=0;
DWORD WINAPI run1(LPVOID p)
{
int counter=0;
int tmp1=0,tmp2=0,r;
double begin=0,end=0,time=0;
begin=GetTickCount();
do
{
tmp1=accnt1;
tmp2=accnt2;
r=rand();
accnt1=tmp1+r;
accnt2=tmp2-r;
counter++;
} while (accnt1+accnt2==0&&counter<1000000);
end=GetTickCount();
time=end-begin;
printf("进?程¨¬1所¨´用®?时º¡À间?为a%lf",time);
counter=0;
return 0;
}
DWORD WINAPI run2(LPVOID p)
{
int counter=0;
int tmp1=0,tmp2=0,r;
double begin=0,end=0,time=0;
begin=GetTickCount();
do
{
tmp1=accnt1;
tmp2=accnt2;
r=rand();
accnt1=tmp1+r;
accnt2=tmp2-r;
counter++;
} while (accnt1+accnt2==0&&counter<1000000);
end=GetTickCount();
time=end-begin;
printf("进?程¨¬2所¨´用®?时º¡À间?为a%lf",time);
counter=0;
return 0;
}
int main(int argc, char *argv[])
{
bool c1=false,c2=false;
int will_wait;
while(1)
{
c1=true;
will_wait=1;
while(c1&&(will_wait==1))
CreateThread(NULL,0,run1,NULL,0,NULL);
break;
{
}
c1 = 0;
break;
}
while(1)
{
c2=true;
will_wait=2;
while(c2&&(will_wait==2))
CreateThread(NULL,0,run2,NULL,0,NULL);
break;
{
}
c2= 0;
break;
}
system("PAUSE");
return 0;
}
运行结果如下:
结果分析:从运行结果中可以看出,实验指导书上所提供的软件实验方案并不能
是进程加解锁。仔细分析运行结果我们可以看出,两个进城所执行的前后并不固
定,这与我们目前所使用电脑的多处理方式有关,此方案并不能成功。