分类: LINUX
2012-10-29 16:58:33
1.问题分析
实现读者写者(Reader-Writer Problem)问题是一个经典的并发程序设计问题,是经常出现的一种同步问题。所谓读者写者问题,是指保证一个writer进程必须与其他进程互斥地访问共享对象的同步问题。
读者写者问题可以这样的描述,有一群写者和一群读者,写者在写同一本书,读者也在读这本书,多个读者可以同时读这本书,但是,只能有一个写者在写书,并 且,读者必写者优先,也就是说,读者和写者同时提出请求时,读者优先。当读者提出请求时需要有一个互斥操作,另外,需要有一个信号量S来当前是否可操作。
1.1 问题详细描述
有一个被许多进程共享的数据区,这个数据区可以是一个文件,或者主存的一块空间,甚至可以是一组处理器寄存器。有一些只读取这个数据区的进程(reader)和一些只往数据区中写数据的进程(writer)。以下假设共享数据区是文件。这些读者和写者对数据区的操作必须满足以下条件:读—读允许;读—写互斥;写—写互斥。这些条件具体来说就是:
(1)任意多的读进程可以同时读这个文件;
(2)一次只允许一个写进程往文件中写;
(3)如果一个写进程正在往文件中写,禁止任何读进程或写进程访问文件;
(4)写进程执行写操作前,应让已有的写者或读者全部退出。这说明当有读者在读文件时不允许写者写文件。
Reader和Writer的同步问题分为读者优先、弱写者优先(公平竞争)和强写者优先三种情况,它们的处理方式不同。
对于读者-写者问题,有三种解决方法:
1、读者优先
除了上述四个规则外,还增加读者优先的规定,当有读者在读文件时,对随后到达的读者和写者,要首先满足读者,阻塞写者。这说明只要有一个读者活跃,那么随后而来的读者都将被允许访问文件,从而导致写者长时间等待,甚至有可能出现写者被饿死的情况。
2、写者优先
除了上述四个规则外,还增加写者优先的规定,即当有读者和写者同时等待时,首先满足写者。当一个写者声明想写文件时,不允许新的读者再访问文件。
3、无优先
除了上述四个规则外,不再规定读写的优先权,谁先等待谁就先使用文件。
1.2 设计的目的和要求
通过对操作系统内核实现代码的阅读、修改、设计,理解和掌握复杂的操作系统的工作原理。
通过研究Linux的线程机制和信号量实现读者写者(Reader-Writer)问题并发控制。
实验条件要求:每人一台与Linux主机联网的Windows主机,普通用户权限。
2.设计思想
创建一个包含n 个线程的控制台进程。用这n 个线程来表示n个读者或写者。每个线程按相应测试数据文件的要求,进行读写操作。请用信号量机制分别实现读者优先和写者优先的读者-写者问题。对于这个问题我们可以有如下的两个思路:
将所有的读者和所有的写者分别放进两个等待队列中,当读允许时就让读者队列释放一个或多个读者,当写允许时,释放第一个写者操作。
读者优先:
如果没有写者正在操作,则读者不需要等待,用一个整型变量readcount记录当前的读者数目,用于确定是否释放写者线程,(当readcout=0 时,说明所有的读者都已经读完,释放一个写者线程),每个读者开始读之前都要修改readcount,为了互斥的实现对readcount 的修改,需要一个互斥对象Mutex来实现互斥。
另外,为了实现写-写互斥,需要一个临界区对象 write,当写者发出写的请求时,必须先得到临界区对象的所有权。通过这种方法,可以实现读写互斥,当readcount=1 时,(即第一个读者的到来时,),读者线程也必须申请临界区对象的所有权。当读者拥有临界区的所有权,写者都阻塞在临界区对象write上。当写者拥有临界区对象所有权时,第一个判断完readcount==1 后,其余的读者由于等待对readcount的判断,阻塞在Mutex上!
写者优先:
写者优先和读者优先有相同之处,不同的地方在:一旦有一个写者到来时,应该尽快让写者进行写,如果有一个写者在等待,则新到的读者操作不能读操作,为此添加一个整型变量writecount,记录写者的数目,当writecount=0时才可以释放读者进行读操作! 为了实现对全局变量writecount的互斥访问,设置了一个互斥对象Mutex3。
为了实现写者优先,设置一个临界区对象read,当有写者在写或等待时,读者必须阻塞在临界区对象read上。
读者除了要一个全局变量readcount实现操作上的互斥外,还需要一个互斥对象对阻塞在read这一个过程实现互斥,这两个互斥对象分别为mutex1和mutex2。
我选择是设计思路是读者优先,当读者和写者同时要访问文件的时候,读者可以访问,写者要处于等待状态。
2.1设计思想描述
我的设计思路大致如下:
优先级: 读者优先(一个读者试图进行读操作时,如果有其他写者在等待进行写操作或正在进行写操作,他要等待该写者完成写操作后才开始读操作)。
将所有的读者和所有的写者分别放进两个等待队列中,当读允许时就让读者队列释放一个或多个读者,当写允许时,释放第一个写者操作。读者写者问题的定义如下:有一个许多进程共享的数据区,这个数据区可以是一个文件或者主存的一块空间;有一些只读取这个数据区的进程(Reader)和一些只往数据区写数据的进程(Writer)。
1、临界区(Critical Section)是一段独占对某些共享资源访问的代码,在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区, 那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达 到用原子方式操作共享资源的目的。
临界区在使用时以CRITICAL_SECTION结构对象保护共享资源,并分别用EnterCriticalSection()和LeaveCriticalSection()函数去标识和释放一个临界区。所用到的CRITICAL_SECTION结构对象必须经过InitializeCriticalSection()的初始化后才能使用,而且必须确保所有线程中的任何试图访问此共享资源的代码都处在此临界区的保护之下。否则临界区将不会起到应有的作用,共享资源依然有被破坏的可能。
2、互斥对象
创建互斥对象
CreateMutex(NULL,FALSE,"mutex_for_readcount");
参数含义如下:
NULL表示创建带有默认安全性的内核对象;
FALSE表示该互斥对象没有被任何线程所拥有;
mutex_for_readcount是为内核对象赋予名字;
ReleaseMutex(h_Mutex); 释放互斥信号。
对资源具有访问权的线程不再需要访问此资源而要离开时,必须通过ReleaseMutex()函数来释放其拥有的互斥对象。
3、定义线程结构:
struct ThreadInfo
{ int Threadhao;
char ThreadClass;
double ThreadStartTime;
double ThreadRunTime;
};
此结构用来存放线程的信息,四个成员变量依次表示线程序号、线程类别、线程开始时间、线程读写持续时间。
4、创建读者线程
CreateThread(NULL,0,\(LPTHREAD_START_ROUTINE)(R_ReaderThread),
\&thread_info[i],0,&thread_ID);
参数含义如下:
NULL表示创建带有默认安全性的内核对象;
0表示新读者线程拥有自己的堆栈,使用缺省大小:1MB;
(LPTHREAD_START_ROUTINE)(R_ReaderThread)表示新读者线程执行的线程函数的地址;
&thread_info[i]表示在线程启动执行时将该参数传递给读者线程函数;
0表示读者线程创建后可以立即进行调度;
&thread_ID表示CreateThread使用这个地址来存放系统分配;
给新读者线程的I D。
5、等待函数:
WaitForMultipleObjects(n_thread,h_Thread,TRUE,-1)
等待函数可使线程自愿进入等待状态,直到一个特定的内核对象变为已通知状态为止
参数含义如下:
n_thread表示线程数量;
h_Thread是指向线程对象句柄的数组的指针;
ture表示:在所有线程对象变为已通知状态之前,该函数将不允许调用线程运行;
参数 -1 告诉系统,调用线程愿意永远等待下去(无限时间量),直到该进程终止运行。
6、程序由两部分组成:
读者-写者模块:包括系统调用接口,读者-写者活动描述主程序。系统接口主要功能是通过管道向父进程发送系统调用命令,并读取父进程送来的返回值。读者-写者活动程序根据临界资源的共享,互斥原则编制,具体见源程序。
主控模块:主控模块实现系统初始化系统调用命令接收与解释执行,系统调用功能的实现(包括信号量机制),及读者-写者活动过程记录与显示。
7、程序流程如下:
1.读取输入参数文件;
2.创建互斥量和信号量;
3.创建线程(挂起状态);
4.恢复线程运行;
5.等待所有线程运行结束, 退出。
具体说明:
1.各读/写者到达时间不同, 用长短不同的睡眠时间表示;
2.各读/写者访问临界资源的持续时间长短不一, 随机生成,
用不同的睡眠时间表示;
2.2 系统平台以及使用语言
我的课程设计实验是在与Linux主机联网的Windows主机(普通用户权限)上面运行的,我选择是是C语言,但是由于编程能力和在Linux下面的操作能力悠闲,所以我最后没有成功,但是在VC下面运行很成功,所以我写的报告的情况都是在windows7下面,编译器是Visual C++ 6.0,我的结果也是在VC下面运行的结果。
3.详细的算法描述
信号量机制是支持多道程序的并发操作系统设计中解决资源共享时进程间的同步与互斥的重要机制,而读者写者问题则是这一机制的一个经典范例。
与记录型信号量解决读者—写者问题不同,信号量机制它增加了一个限制,即最多允许RN个读者同时读。为此,又引入了一个信号量L,并赋予初值为RN,通过执行wait(L,1,1)操作,来控制读者的数目,每当有一个读者进入时,就要执行wait(L,1,1)操作,使L的值减1。当有RN个读者进入读后,L便减为0,第RN+1 个读者要进入读时,必然会因wait(L,1,1)操作失败而堵塞。对利用信号量来解决读者—写者问题的描述如下:
Var RN integer;L,mx:semaphore: =RN,1;
Begin
Parbegin
Reader :begin
Repeat
Swait(L,1,1);
Swait(mx,1,0);
.
Perform reader operation;
Ssignal(L,1);
Until false;
End
Writer :begin
Repeat
Swait(mx ,1,1,l,RN,0);
Perform writer operation;
Ssignal(mx,1);
Until false;
End
Parend
End
其中,Swait(mx,1,0)语句起着开关作用,只要无Writer进程进入些,mx=1,reader进程就都可以进入读。但是要一旦有Writer进程进入写时,其MX=0,则任何reader进程就都无法进入读。Swait(mx ,1,1,l,RN,0)语句表示仅当既无Write进程在写(mx=1),又无reader进程在读(L=RN)时,writer进程才能进入临界区写。
4.源程序和详细的注释
#include
#include
#include
#define MAX_NUM 100
#define LINESIZE 80
struct rwparm {
int seq;
int rwtype;
float arvtime;
}rwparms[MAX_NUM];
#define FIRSTARVTIME 0
#define LASTARVTIME 300
#define MINDURATION 2000
#define MAXDURATION 10000
int rcnt = 0;
HANDLE hMutex;
HANDLE hWSem;
clock_t curclk;
DWORD WINAPI reader(LPVOID lpParm);
DWORD WINAPI writer(LPVOID lpParm);
int getparm(HANDLE *hThread);
int main(int argc, char *argv[])
{
HANDLE hThread[MAX_NUM];
DWORD dwId;
int rwnum;
int i;
rwnum = getparm(hThread);
if(rwnum == 0)
{
printf("No parameters input.\n");
exit(0);
}
srand(time(NULL));
curclk = clock();
hMutex = CreateMutex(NULL, FALSE, NULL);
if(hMutex == NULL)
{
printf("Error create mutex.\n");
exit(-1);
}
hWSem = CreateSemaphore(NULL, 1, 1, NULL);
if(hWSem == NULL)
{
printf("Error create wsem.\n");
exit(-1);
}
for(i = 0; i < rwnum; i++)
{
if(rwparms[i].rwtype == 'R')
hThread[i] = CreateThread(NULL, 0, reader, &rwparms[i], CREATE_SUSPENDED, &dwId);
else
hThread[i] = CreateThread(NULL, 0, writer, &rwparms[i], CREATE_SUSPENDED, &dwId);
}
printf("Starting at %d...\n", curclk);
for(i = 0; i < rwnum; i++)
ResumeThread(hThread[i]);
WaitForMultipleObjects(rwnum, hThread, TRUE, INFINITE);
printf("All done.\n");
}
void rlock(int seq)
{
if(WaitForSingleObject(hMutex, INFINITE) != WAIT_OBJECT_0)
{
printf("!!!Reader(%d): Wait mutex failed.\n", seq);
exit(-1);
}
if(++rcnt == 1)
{
if(WaitForSingleObject(hWSem, INFINITE) != WAIT_OBJECT_0)
{
ReleaseMutex(hMutex);
printf("!!!Reader(%d): Wait wsem failed.\n", seq);
exit(-1);
}
}
ReleaseMutex(hMutex);
}
void runlock(int seq)
{
if(WaitForSingleObject(hMutex, INFINITE) != WAIT_OBJECT_0)
{
printf("!!!Reader(%d): Wait mutex failed.\n", seq);
exit(-1);
}
if(--rcnt==0)
{
ReleaseSemaphore(hWSem, 1, NULL);
}
ReleaseMutex(hMutex);
}
DWORD WINAPI reader(LPVOID lpParm)
{
int i, total;
int durtime;
struct rwparm * rwsp;
rwsp = (struct rwparm *)lpParm;
durtime = (rand() % (MAXDURATION + 1 - MINDURATION)) + MINDURATION;
Sleep(rwsp->arvtime * 1000);
printf("@ Reader(%d) coming at %.3f\n", rwsp->seq, rwsp->arvtime);
rlock(rwsp->seq);
printf("> Reader(%d) enters CS at %.3f (CS=%d)\n", \
rwsp->seq, (float)clock()/CLOCKS_PER_SEC, curclk);
fflush(stdout);
Sleep(durtime);
printf("< Reader(%d) leaves CS at %.3f (CS=%d)\n", \
rwsp->seq, (float)clock()/CLOCKS_PER_SEC, curclk);
fflush(stdout);
runlock(rwsp->seq);
}
void wlock(int seq)
{
if(WaitForSingleObject(hWSem, INFINITE) != WAIT_OBJECT_0)
{
printf("!!!Writer(%d): Wait wsem failed.\n", seq);
exit(-1);
}
}
void wunlock()
{
ReleaseSemaphore(hWSem, 1, NULL);
}
DWORD WINAPI writer(LPVOID lpParm)
{
int durtime;
clock_t oldclk;
struct rwparm * rwsp;
rwsp = (struct rwparm *)lpParm;
durtime = (rand() % (MAXDURATION + 1 - MINDURATION)) + MINDURATION;
Sleep(rwsp->arvtime * 1000);
printf("Writer(%d) coming at %.3f\n", rwsp->seq, rwsp->arvtime);
wlock(rwsp->seq);
printf("=Writer(%d) enters CS at %.3f\n",
rwsp->seq, (float)clock()/CLOCKS_PER_SEC);
fflush(stdout);
Sleep(durtime);
oldclk = curclk;
curclk = clock();
printf("=Writer(%d) leaves CS at %.3f(update CS from %d to %d)\n", \
rwsp->seq, (float)clock()/CLOCKS_PER_SEC, oldclk, curclk);
fflush(stdout);
wunlock();
}
int getparm(HANDLE *hThread)
{
int i,j;
char lnbuf[LINESIZE+1];
printf("Input parameters for readers and writers: \n");
i = 0;
while( *fgets(lnbuf, LINESIZE, stdin) != 'a')
{
j = sscanf(lnbuf, "%c %f", &(rwparms[i].rwtype), &(rwparms[i].arvtime));
if( j != 2 )
{
printf("Input 2 parameters in one line.\n");
continue;
}
if ( (toupper(rwparms[i].rwtype) != 'R') && (toupper(rwparms[i].rwtype) != 'W') )
{
printf("Invalid thread type: %c(X)\n", \
rwparms[i].rwtype,rwparms[i].rwtype);
continue;
}
if( (rwparms[i].arvtime < FIRSTARVTIME) || (rwparms[i].arvtime > LASTARVTIME) )
{
printf("Invalid arriving time: %.2f\n", rwparms[i].arvtime);
continue;
}
rwparms[i].rwtype = toupper(rwparms[i].rwtype);
rwparms[i].seq= i;
// printf("%c, %.2f\n", toupper(rwparms[i].rwtype), rwparms[i].arvtime);
i++;
if(i >= MAX_NUM)
break;
}
return i;
}