大纲:
零。关于同步与互斥的多线程编程(一般思路)
1. 申请“信号量”句柄,初始化信号量。 (HANDLE ...)
2. 创建使用上线程的函数(函数中需要用到信号量)。(CreateSemaphore(...))
3. 创建线程(创建线程时用上第二步的函数)。(CreateThread(...))
4. 等待结束线程。(WaitForSingleObject(...))
5. 关闭句柄。(CloseHandle(...))
一。放水果问题
1. 程序演示
2. 程序运行效果
3. 运行结果简要分析
二。生产者与消费者问题
1. 程序演示
2. 程序运行效果
3. 运行结果简要分析
内容:
一。放水果问题
1. 程序演示
// fruitThread.cpp
/**
* 题目要求:
* 桌子上有一只盘子,只允许存放一个水果,父亲专向盘中放水果,母亲专向盘中放桔子,
* 儿子专等吃盘里的桔子,女儿专等吃盘里的苹果。只要盘子空,则父亲母亲可向盘中放水果,
* 仅当盘中有自己需要的水果时,儿子或女儿可从中取出。
* 请给出四人之间的同步关系,并用PV操作实现四人正确活动的程序。
**/
#include <iostream>
#include <windows.h>
using namespace std;
// 信号量句柄声明
HANDLE emptyMutex;
HANDLE orangeMutex;
HANDLE fruitMutex;
int main()
{
// 创建需要用到的信号量
// 函数的第2个参数表示信号量的初始值,第3个参数表示信号量的最大数值
emptyMutex = CreateSemaphore(NULL, 1, 1, NULL);
orangeMutex = CreateSemaphore(NULL, 0, 1, NULL);
fruitMutex = CreateSemaphore(NULL, 0, 1, NULL);
// 声明线程需要用到的函数
DWORD WINAPI fatherThread(LPVOID IpParameter);
DWORD WINAPI motherThread(LPVOID IpParameter);
DWORD WINAPI sonThread(LPVOID IpParameter);
DWORD WINAPI daughterThread(LPVOID IpParameter);
cout << "盘子取放水果游戏开始." << endl;
// 创建线程
HANDLE fThread = CreateThread(NULL, 0, fatherThread, NULL, 0, NULL);
HANDLE mThread = CreateThread(NULL, 0, motherThread, NULL, 0, NULL);
HANDLE sThread = CreateThread(NULL, 0, sonThread, NULL, 0, NULL);
HANDLE dThread = CreateThread(NULL, 0, daughterThread, NULL, 0, NULL);
// 等待线程结束
WaitForSingleObject(fThread, INFINITE);
WaitForSingleObject(mThread, INFINITE);
WaitForSingleObject(sThread, INFINITE);
WaitForSingleObject(dThread, INFINITE);
// 关闭句柄
CloseHandle(fThread);
CloseHandle(mThread);
CloseHandle(sThread);
CloseHandle(dThread);
CloseHandle(emptyMutex);
CloseHandle(orangeMutex);
CloseHandle(fruitMutex);
cout << "\n盘子取放水果游戏结束." << endl;
system("pause");
return 0;
}
DWORD WINAPI fatherThread(LPVOID IpParameter)
{
for(int i = 0; i < 5; ++i){
WaitForSingleObject(emptyMutex, INFINITE); // P操作
// 开始临界区
cout << "\nFather往盘中放一个苹果\n";
// 结束临界区
ReleaseSemaphore(fruitMutex, 1, NULL); // V操作
}
return 0;
}
DWORD WINAPI motherThread(LPVOID IpParmeter)
{
for(int i = 0; i < 5; ++i){
WaitForSingleObject(emptyMutex, INFINITE); // P操作
// 开始临界区
cout << "\nMother往盘中放一个桔子\n";
// 结束临界区
ReleaseSemaphore(orangeMutex, 1, NULL); // V操作
}
return 0;
}
DWORD WINAPI sonThread(LPVOID IpParmeter)
{
for(int i = 0; i < 5; ++i){
WaitForSingleObject(orangeMutex, INFINITE); // P操作
// 开始临界区
cout << "Son往盘中取一个桔子\n";
// 结束临界区
ReleaseSemaphore(emptyMutex, 1, NULL); // V操作
}
return 0;
}
DWORD WINAPI daughterThread(LPVOID IpParmeter)
{
for(int i = 0; i < 5; ++i){
WaitForSingleObject(fruitMutex, INFINITE); // P操作
// 开始临界区
cout << "Daughter往盘中取一个苹果\n";
// 结束临界区
ReleaseSemaphore(emptyMutex, 1, NULL); // V操作
}
return 0;
}
2. 程序运行效果
3. 运行结果简要分析
首先由结果可以看见的是,它们已经按题目要求输出了,Father放入的是苹果,Mother放入的是Orange,而Son取的是Orange,Daughter取的是苹果,这里一共有十次的放入操作,分别是五次父亲的放入及五次母亲的放入,还有十次的取出操作,分别是女儿的取出还有儿子的取出,而这些放入与取出,都遵循着这么一个规律,“取”建立在“放”之前,而且必是一放一取,也就是满足了题目要求中的盘子一次只能有一个水果的要求。
二。生产者与消费者问题
1. 程序演示
// ProducerAndConsumerThread.cpp
/**
* 题目要求:
* 有两组进程共享一个环形的缓冲池。一组进程被称为生产者,另一组进程被称为消费者。
* 缓冲池是由若干个(实验中可假设为4个)大小相等的缓冲区组成的,每个缓冲区可以容纳
* 一个产品。生产者时空程不断地将生产的产品放入缓冲池,消费者进程不断地将产品从缓冲池
* 中取出。用PV操作实现和生产者消费者正确活动的程序。
**/
#include <iostream>
#include <string.h>
#include <windows.h>
using namespace std;
// 信号量句柄声明
HANDLE emptyMutex;
HANDLE fullMutex;
HANDLE bufferMutex;
// 缓冲区及相关数目计算变量声明
char buffer[] = "123456";
const int maxBufferNumber = strlen(buffer) - 1;
int nowUsingBufferNumber(0);
int main()
{
// 创建需要用到的信号量
// 函数的第2个参数表示信号量的初始值,第3个参数表示信号量的最大数值
emptyMutex = CreateSemaphore(NULL, maxBufferNumber, maxBufferNumber, NULL);
fullMutex = CreateSemaphore(NULL, 0, maxBufferNumber, NULL);
bufferMutex = CreateSemaphore(NULL, 1, 1, NULL);
// 声明线程需要用到的函数
DWORD WINAPI ProducerThread(LPVOID IpParameter);
DWORD WINAPI ConsumerThread(LPVOID IpParameter);
cout << "-> 生产者、消费者问题开始.\n" << endl;
// 创建线程
HANDLE pThread = CreateThread(NULL, 0, ProducerThread, NULL, 0, NULL);
HANDLE cThread = CreateThread(NULL, 0, ConsumerThread, NULL, 0, NULL);
// 等待线程结束
WaitForSingleObject(pThread, INFINITE);
WaitForSingleObject(cThread, INFINITE);
// 关闭句柄
CloseHandle(pThread);
CloseHandle(cThread);
CloseHandle(emptyMutex);
CloseHandle(fullMutex);
CloseHandle(bufferMutex);
cout << "\n-> 生产者、消费者问题结束." << endl;
system("pause");
return 0;
}
DWORD WINAPI ProducerThread(LPVOID IpParmeter)
{
int n = 10;
while(n--){
WaitForSingleObject(emptyMutex, INFINITE); // P(缓冲区上有空位)
WaitForSingleObject(bufferMutex, INFINITE); // P(资源未被占用)
nowUsingBufferNumber = (nowUsingBufferNumber + 1) % maxBufferNumber;
buffer[nowUsingBufferNumber] = nowUsingBufferNumber + 'A';
cout << "Hey, 生产者正在为你放入第" << nowUsingBufferNumber << "个资源"
<< ",资源的内容为" << buffer[nowUsingBufferNumber]
<< " ( put put put .... )" << endl;
ReleaseSemaphore(bufferMutex, 1, NULL); // V(资源占用完毕)
ReleaseSemaphore(fullMutex, 1, NULL); // V(释放满的资源信号量)
}
return 0;
}
DWORD WINAPI ConsumerThread(LPVOID IpParameter)
{
int n = 10;
while(n--){
WaitForSingleObject(fullMutex, INFINITE); // P(缓冲区上有资源可提取)
WaitForSingleObject(bufferMutex, INFINITE); // P(资源未被占用)
cout << "Yoo, 消费者正在取出第" << nowUsingBufferNumber << "个资源"
<< ",资源的内容为" << buffer[nowUsingBufferNumber]
<< " (get...)" << endl;
nowUsingBufferNumber = (nowUsingBufferNumber - 1 + maxBufferNumber) % maxBufferNumber;
ReleaseSemaphore(bufferMutex, 1, NULL); // V(资源占用完毕)
ReleaseSemaphore(emptyMutex, 1, NULL); // V(释放空的资源信号量
}
return 0;
}
2. 程序运行效果
3. 运行结果简要分析
可以看见的是,这里共执行了十次的生产者放入资源操作与十次的消费者取出资源的操作,在缓冲池有数据的时候,消费者可以从缓冲区取出资源,刚刚开始时,生产者先放入资源“B”,由于缓冲区未满,所以生产者仍可继续再放入资源“C”,这时,虽然生产者可以继续放入资源,但是,由于消费者线程先执行操作了,从缓冲区中取出资源,所以,生产者的放入资源操作会在消费者的取出操作完成后再进行,按照生产者缓冲区有非满且非在进行操作则可放,消费者缓冲区非空且非在进行操作则可取的原则,这两个进行不断地运行循环,直到完成循环中设定的次数,最后,由于程序中的生产者与消费者的操作数目设定相同,所以,到最后会将缓冲区清空后退出程序。