昨天接到操作系统课程设计题目,是用信号量同步进程(线程)的,由于要用到Windows API ,好几个同学问我API是干什么的,由于大家都没接触过,不知怎么用,所以我就以课设题目为例,给大家介绍一下API的一些基本知识。但请您注意,本文旨在介绍API使用,而不是给您提供现成的代码让您直接复制到课程设计报告中去,您应该只把这做为一份参考,然后写出自己的代码。好了,我们从最基本的讲起,首先看题目:
Linux或Windows或Unix环境下,采用系统调用中的信号量、P、V操作,编程解决以问题。读者与写者问题。一个数据集为多个并发进程所共享,其中一些进程只要求读该数据集的内容,这些进程称为“读者”,而另一些进程则要求修改该数据集的内容,这些进程称为“写者”。具体要求是:允许多个读者同时读该数据集的内容;若有一个写者在写,则其他读者不能读;若一个写者在写或有其他读者在读,则其他写者均被拒绝;当一个写者正在写,而有多个读者与写者在等待时,写者应优先唤醒。请用P、V操作写出进程的同步算法。要求打印:初始状态,中间变化的状态信息,以及最终状态信息。
我以Windows做为示例,考虑到大家并非都懂C++,故代码用C写。分析:
1.理想状态:我们程序中完全和作业中的方式一样,比如定义信号量用 semaphore mutex; P操作就直接P(mutex),V操作就直接V(mutex),尽量避免直接使用API。
好的,我们首先实现这两种功能。由于笔者平时开发习惯,都会在特定的工程中的函数、数据类型加上工程标志,这个项目是Operating system Course Design,我就取OSCD,做为函数和数据类型前缀。所以上面的semaphore mutex;定义信号量的方式就变为OSCD_semaphore mutex(只是换了个衣服而已)。下面介绍第一个API函数(为了先避免字符编码的问题,看我这个不要对函数名后的A太过在意,以后接触Windows编程后,你自然就懂了):
HANDLE CreateSemaphoreA(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,LONG lInitialCount,LONG lMaximumCount,LPCSTR lpName );首先看一下这个中的数据类型;其实 LONG 就是typedef long LONG; LPCSTR 就是char * (字符串的指针),LPSECURITY_ATTRIBUTES 和HANDLE目前不是一两句话能将清楚的,LPSECURITY_ATTRIBUTES 可以先直接忽略掉,你可以把HANDLE 理解为操作信号的“手柄”,也可以先理解为指向信号量的类型,以后我们对信号量的操作就是通过它来完成的,在直接一点,我们就可以把它就当作是信号量,其中lInitialCount就好比我们作业里mutex.value,中的value,我们需要在调用这个函数时给它赋初值,lMaximumCount是指定信号量的最大值,本例中我们取10,最后一个参数lpName 课设用不到,直接传NULL。回过头来看看这个函数实现的功能:创建一个信号量并返回,在创建信号量时我们必须对信号量赋初值。也就是说我们真正用到的参数只有一个lInitialCount,下面我们对其封装;
#define MAX_SEM_COUNT 10 // 信号量最大值我们取10
OSCD_semaphore OSCD_CreateSemaphore( int nInitialCount) // 参数是初值,返回的是信号量。
{
OSCD_semaphore hSemaphore;
if (nInitialCount>MAX_SEM_COUNT){ //
printf("semaphore count too big ,please specify a value range 0 to 10\n");
exit(0);
}
hSemaphore=::CreateSemaphoreA(NULL,nInitialCount,MAX_SEM_COUNT,NULL ); // 真正创建信号量
if (hSemaphore == NULL) {
printf("CreateSemaphore error: %d\n", GetLastError());
exit(0);
}
return hSemaphore;
}
拿本例中的 mutex 为例;我们只需要这么初始化它:OSCD_semaphore mutex;//定义mutex, mutex= OSCD_CreateSemaphore(1);//初始化;下面是删除信号量的函数:
{
::CloseHandle(semaphore); // 你可以认为这个API可以删除掉用上面函数创建的信号量,要删除那个信号量,参数就是哪个信号量,比如OSCD_DelSemaphore(mutex);
}
P操作:按照P操作的定义,先对信号量值减1,然后再和0比较,若大于等于0,就可以得到资源继续运行,小于0的话就等待。我先给出代码再做解释:
{
::WaitForSingleObject(semaphore,INFINITE); //这个API是等待信号量的函数,若等待的信号量值大于等于0时,便返回,程序继续运行,若无资源了,便会一直等待,函数不会返回,程序就不能再往下运行
}
V操作:使信号量加1;很简单:
{
if (!::ReleaseSemaphore(semaphore,1,NULL)) // 该API使信号量加1(第二个参数,如果你想使它一次加2,只需给第二个参数传2 即可)
{
printf("release semaphore error: %d\n",GetLastError()); // 检查上面函数是否调用成功
exit(0);
}
} // 该函数使用方法,还以mutex为例说明:V(mutex);
至此我们的初步目标已经实现,接下来就是创建线程的函数;
写者的具体代码…
}
char * p="DataToWrite";
HANDLE hThreads=::CreateThread(NULL,0,Reader,p,SW_NORMAL,NULL); // 只需注意第3、4个参数就行,其它的参数不要管。
这样线程创建就完了;但看一下这种情况 //把创建线程作为最后一个语句时main函数会在创建完线程后退出,也就意味着程序结束,但这是线程只是创建成功,但并没有执行的机会。
………..
HANDLE hThreads=::CreateThread(NULL,0,Reader,p,SW_NORMAL,NULL);
}
要解决这个问题,就要用到另一个API,在这个实验中一共有多个线程,我们必须在创建完线程后等待它们全部运行结束后才能让main函数退出。
WaitForMultipleObjects( DWORD nCount, HANDLE* lpHandles, BOOL bWaitAll, DWORD dwMilliseconds);这个函数中第一个参数指得是要等待线程的总数量,第二个参数是所有线程的“手柄”数组,因为等待函数是通过线程的“手柄”才能操作线程,等待函数也一样,只有得到它的句柄才能在其内核对象上等待;所以我们应该这样做:
HANDLE hThreads[12];//本例中有六个读者线程,六个写者线程;
然后再创建线程成功后把每个线程的句柄都保存在这个数组中WaitForMultipleObjects( DWORD nCount, HANDLE* lpHandles, BOOL bWaitAll, DWORD dwMilliseconds);然后再这样调用:
WaitForMultipleObjects(12,hThreads,TRUE,INFINITE); //这个函数会在所有等待的线程都执行完成后返回,接着main函数返回,程序结束。由于这个函数也可用于等待其中任一形成结束,后返回(第三个参数传FALSE时,意思是不必等所有线程都执行完,只要有一个执行完,就返回),所以我们在这传TRUE,并且在最后一个参数(等待超时,如果在这个时间内线程还没有结束,该函数也会返回,所以我们应该把超时设置为无限。。)
到了这里,基本都讲完了,下面给出完整代码
/* *****************************************************************
Student Number: 3100604012
Name: Duwen
Date:2012-6-9
Type:Demonstration code
Description:Operating System Course Design (OSCD)---Thread synchronization
with semaphore. you must know this code only as a demonstration of the Windows
API , it's unwise to copy this code to OSCD report directly, you should regard this
code as a reference to you. and then write it by yourself.
****************************************************************** */
#include "stdafx.h"
#include <Windows.h>
#define MAX_SEM_COUNT 10
#pragma warning(disable:4996) // Ignore 4996 warning
typedef HANDLE OSCD_semaphore;
/////////////////////////////////////////////////////////////////////////// /
// Define the semaphores needed as global variables
OSCD_semaphore mutex,reader,writer;
// Define the counters
int readcount=0, writecount=0,readapp=0;
// Allocate buffer
char buffer[256]={0};
////////////////////////////////////////////////////////////////// /
// Function to create and initialize a new semaphore
OSCD_semaphore OSCD_CreateSemaphore( int nInitialCount)
{
OSCD_semaphore hSemaphore;
if (nInitialCount>MAX_SEM_COUNT){ // sanity check
printf("semaphore count too big ,please specify a value range 0 to 10\n");
exit(0);
}
hSemaphore=::CreateSemaphoreA(NULL,nInitialCount,MAX_SEM_COUNT,NULL );
if (hSemaphore == NULL) {
printf("CreateSemaphore error: %d\n", GetLastError());
exit(0);
}
return hSemaphore;
}
// Function to delete the semaphore
void OSCD_DelSemaphore( OSCD_semaphore semaphore)
{
::CloseHandle(semaphore);
}
// P operation
void P(OSCD_semaphore semaphore)
{
::WaitForSingleObject(semaphore,INFINITE);
}
// V operation
void V(OSCD_semaphore semaphore)
{
if (!::ReleaseSemaphore(semaphore,1,NULL))
{
printf("release semaphore error: %d\n",GetLastError());
exit(0);
}
}
// Reader thread function
DWORD WINAPI Reader(LPVOID lpParameter)
{
char *pThreadName=( char *)lpParameter;
Sleep((pThreadName[6]-'0')*5);
P(mutex);
if (writecount>0||buffer[0]==NULL){
if(writecount>0)
printf("%s find there exists writer(s) trying to write , wait \n",pThreadName);
else
printf("There is no data in buffer now,%s wait \n" ,pThreadName);
readapp++;
V(mutex);
P(reader);
printf("%s gets semaphore successfully !",pThreadName);
P(mutex);
readapp--;
readcount++;
if (readapp>0) V(reader);
V(mutex);
}
else{
readcount++;
V(mutex);
}
printf("%s read the data that :%s\n",pThreadName,buffer);
P(mutex);
readcount -- ;
if ( readcount == 0 && writecount>0 )
V(writer) ;
V(mutex) ;
return 0;
}
// Writer thread function
DWORD WINAPI Writer(LPVOID lpParameter)
{
char *pDataToWrite=( char *)lpParameter;
Sleep((pDataToWrite[7]-'0')*7);
P(mutex);
writecount ++ ;
printf("A new writer is trying to write \n");
// when there are readers are reading or writers are trying to write, waiting..
if (readcount>0 || writecount>1 ){
V(mutex);
printf(" Waiting(writer) semaphore\t The contents to write is:%s\n ", pDataToWrite);
P(writer);
}
else V(mutex) ; // skip waiting
int nLen=strlen(pDataToWrite);
strncpy(buffer,pDataToWrite,nLen/2); // write
Sleep(3); // wait 3 ms
strcpy(buffer+nLen/2,pDataToWrite+nLen/2);
P(mutex);
writecount -- ;
if (writecount>0 ) V(writer) ;
else if (readapp>0 ) V(reader) ;
printf("A writer waited gets semaphore ,Writing: \" %s \" to buffer \n ", pDataToWrite );
V(mutex) ;
return 0;
}
int _tmain( int argc, _TCHAR* argv[])
{
// Create semaphores
mutex= OSCD_CreateSemaphore(1);
reader=OSCD_CreateSemaphore(0);
writer=OSCD_CreateSemaphore(0);
int i,j;
char *DataToWrite[6]={
"(writer1) ","(writer2)","(writer3)",
"(writer4) ","(writer5)","(writer6)"
};
char *ReaderName[6]={
"reader1","reader2","reader3","reader4","reader5","reader6"
};
HANDLE hThreads[12];
for ( i=0,j=0;i<6;i++)
{
hThreads[j]=::CreateThread(NULL,0,Reader,ReaderName[i],SW_NORMAL,NULL);
if(!hThreads[j++]){ printf("create thread failed\n") ; return 0;}
hThreads[j]=::CreateThread(NULL,0,Writer,DataToWrite[i],SW_NORMAL,NULL);
if(!hThreads[j++]){ printf("create thread failed\n"); return 0;}
}
printf("CREATE THREADS COMPLETED\n");
// Wait for all the threads ending up.and then exit.
WaitForMultipleObjects(12,hThreads,TRUE,INFINITE);
// Delete semaphores
OSCD_DelSemaphore(mutex);
OSCD_DelSemaphore(writer);
OSCD_DelSemaphore(reader);
return 0;
}