logo资料库

操作系统实验二 -------信号量.docx

第1页 / 共5页
第2页 / 共5页
第3页 / 共5页
第4页 / 共5页
第5页 / 共5页
资料共5页,全文预览结束
1. 问题描述及需求分析 以生产者-消费者模型为基础,在 Windows 环境下创建一个控制台进程 (或者界面进程),在该进程中创建读者写者线程模拟生产者和消费者。写 者线程写入数据,然后将数据放置在一个空缓冲区中供读者线程读取。读者 线程从缓冲区中获得数据,然后释放缓冲区。当写者线程写入数据时,如果 没有空缓冲区可用,那么写者线程必须等待读者线程释放出一个空缓冲区。 当读者线程读取数据时,如果没有满的缓冲区,那么读入线程将被阻塞,直 到新的数据被写进去。 2. 实验设计 缓冲区是临界资源,不论是生产者还是消费者访问临界资源的时候都需 要互斥的访问。 对于访问临界资源必须有个互斥信号量 mutex,其初始值为 1,表示可以访问。 对于临界资源的访问不分生产者还是消费者,都是一个 进程访问临界资源的时候其他进程需要等待。 生产者与消费者是互相合作的关系,我们为完成某种任务而建立多个进 程,这些进程因为要在某些位置上协调他们的工作次序而等待,比如说 A 进 程要工作必须等待 B 进程的一个结果,如果仅仅是 A 进程单方面的需要 B 进 程的一个结果,那这张制约关系就是 A 制约 B 的,如果同时 B 进程的工作也 需要 A 进程工作的结果,那么就是 A 与 B 互相制约了。 生产者-消费者问题里的同步关系我认为是 A 与 B 互相制约的情况,因为 生产者要生产的前提是缓冲区没满,而缓冲区没满是消费者运行后的结果, 同样消费者要运行的前提是缓冲区不空,而缓冲区不空是生产者不断生成的 结果。本题的同步关系需要两个信号量。一个是消费者通知生产者是否可以 生产的“缓冲区空”信号量 empty,一个是生产者通知消费者要消费的“缓 冲区满”信号量 full。 3. 实验代码 #include #include #include #include using namespace std; #define STD __stdcall #define LENGTH 20
#define GETMYRAND() (int)(((double)rand()/(double)RAND_MAX)*300) //使用临界区来同步线程 CRITICAL_SECTION _cr; //空信号量 HANDLE emptySemaphore = NULL; //满信号量 HANDLE fullSemaphore = NULL; //缓冲区 Buffer vector buffer; //计划生产数 int planNum=20; //实际生产数 int num = 0; // 消费者线程 DWORD STD Consumer(void* lp) { while(true) { //等待判断缓冲区满的信号量 WaitForSingleObject(fullSemaphore,0xFFFFFFFF); //进入临界区,线程同步,功能同互斥量 EnterCriticalSection(&_cr); //消费者线程从缓冲区中取出消费一个资源 buffer.pop_back(); //打印当前缓冲区可用资源数 cout << "消费者消费资源,缓冲区资源:" << buffer.size() << endl; //离开临界区 LeaveCriticalSection(&_cr); //释放判断缓冲区空的信号量 ReleaseSemaphore(emptySemaphore,1,NULL); //线程睡眠随机时间 Sleep(GETMYRAND());
} return 0; } // 生产者线程 DWORD STD Producer(void* lp) { while(num < planNum){ //等待判断缓冲区空的信号量 WaitForSingleObject(emptySemaphore, 0xFFFFFFFF); //进入临界区,线程同步,功能同互斥量 EnterCriticalSection(&_cr); //生产者线程向缓冲区中生成一个资源 buffer.push_back(1); num++; //打印当前缓冲区可用资源数 cout << "生产者生产资源,缓冲区资源:" << buffer.size() << endl; //离开临界区 LeaveCriticalSection(&_cr); //释放判断缓冲区满的信号量 ReleaseSemaphore(fullSemaphore, 1, NULL); //线程睡眠随机时间 Sleep(GETMYRAND()); if(num == planNum){ cout << "\n 生产者已经完成计划生产数:" << num <<"\n"<< endl; } } } return 0; int main() { //创建信号量
emptySemaphore = CreateSemaphore(NULL, LENGTH, LENGTH, NULL); fullSemaphore = CreateSemaphore(NULL, 0, LENGTH, NULL); //初始化临界区 InitializeCriticalSection(&_cr); //开启多线程 HANDLE handles[3]; handles[2] = CreateThread(0, 0, &Producer, 0, 0, 0); handles[1] = CreateThread(0, 0, &Producer, 0, 0, 0); handles[0] = CreateThread(0, 0, &Consumer, 0, 0, 0); //等待子线程执行完毕 WaitForMultipleObjects(3, handles, true, INFINITE); //"Join" trreads //释放子线程 CloseHandle(handles[0]); CloseHandle(handles[1]); CloseHandle(handles[2]); //释放临界区 DeleteCriticalSection(&_cr); return 0; } 4. 实验结果与分析
实验过程: 1、首先创建空、满缓冲区信号量 2、初始化临界区(P(mutex)和 V(mutex)之间) 3、创建生产者,消费者子线程(2 个生产者,1 个消费者) 4、在判断缓冲区为空时向缓冲区存入商品 5、在判断缓冲区为满时从缓冲区中拿出商品 6、每一轮执行存入或拿出商品都显示缓冲区资源数 7、释放生产者、消费者子线程 8、释放临界区 因为有多个线程的生产者和消费者,所以第一个生产者发现生产数目达 到计划生产数后,其他生产者还会继续判断缓冲区是否已经满的信息但是发 现总生产数已经达到计划生产数,就再次显示出生产者已经完成计划生产数 的提示。 程序中的 P(mutex)和 V(mutex)必须成对出现,夹在两者之间的代码段是 临界区;施加于信号量 empty 和 full 上的 PV 操作也必须成对出现,但分别 位于不同的程序中。在这个问题中,P 操作的次序是很重要的,如果把生产 者进程中的两个 P 操作交换次序,那么,当缓冲区中存满 k 件产品(empty=0, mutex=1,full=k)时,生产者又产生一件产品,在它预想缓冲区存放时,将 在 P(mutex)上等待,由于此时 mutex=0,它已经占有缓冲区,这是消费者欲 取产品将停留在 P(mutex)上而得不到使用缓冲区的权力。这就导致生产者永 远等待消费者取走产品,而消费者却在等待生产者释放缓冲区过得占有权, 这种相互之间的等待永远不可能结束。所以,在使用信号量和 PV 操作实现进 程同步时,要特别当心 P 操作的次序,而 V 操作的次序无关紧要。一般来说, 用于互斥的信号量上的 P 操作总在后面执行。 5. 实验心得 通过本次实验,我对生产者-消费者问题的相关知识有了更加深刻的理解, 进一步掌握了进程的同步的相关概念,理解了利用信号量机制解决进程同步 问题的基本方法。队列和链表在 C++中是可以直接用已经有的模板的。消费 者要是能放到缓冲区里就看生产者等待队列里有没有人,有人就唤醒一个生 产者。要是缓冲区只有 1 那么大,最终完成链表中必定是生产者和消费者交 替,这是一个验证程序对错的方法。可以定义两个互斥信号量 mutex1 和 mutex2 来代替 mutex,他们分别用于多个生产者和多个消费者工作时的各自 互斥,这样可以使生产者-消费者问题的并发性进一步提高。
分享到:
收藏