一、实验目的:
1. 掌握基本的同步与互斥算法,理解P,V操作。
2. 理解生产者消费者模型,了解其它典型的同步互斥模型,如哲学家就餐、读者-写者模型等。
3. 了解LINUX中进程同步互斥的实现方法,掌握相关函数的使用方法。
4. 学习使用Windows中基本的同步对象,掌握相关API的使用方法。
5. 了解Windows中多线程的并发执行机制,实现进程的同步与互斥。
二、实验环境:
Windows环境
三、实验内容:
PART 2 Windows环境
一、相关知识介绍
1. 同步对象
同步对象是指Windows中用于实现同步与互斥的实体,包括信号量(Semaphore)、互斥量(Mutex)、临界区(Critical Section)和事件(Events)等。本实验中使用到信号量、互斥量和临界区三个同步对象。
同步对象的使用步骤:
创建/初始化同步对象。
请求同步对象,进入临界区(互斥量上锁)。
释放同步对象(互斥量解锁)。
这些对象在一个线程中创建,在其他线程中都可以使用,实现同步与互斥。
2. 相关API的功能及使用
利用Windows SDK提供的API编程,而VC中包含有Windows SDK的所有工具和定义。要使用这些API,需要包含对这些函数进行说明的SDK头文件——最常见的是Windows.h(特殊的API调用还需要包含其他头文件)。
下面给出相关API的功能和使用方法简单介绍。
(1) CreateThread
功能——创建一个在调用进程的地址空间中执行的线程
格式
HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParamiter,
DWORD dwCreationFlags,
Lpdword lpThread );
参数说明
lpThreadAttributes——指向一个LPSECURITY_ATTRIBUTES(新线程的安全性描述符)。
dwStackSize——定义原始堆栈大小。
lpStartAddress——指向使用LPTHRAED_START_ROUTINE类型定义的函数。
lpParamiter——定义一个给进程传递参数的指针。
dwCreationFlags——定义控制线程创建的附加标志。
lpThread——保存线程标志符(32位)
(2) CreateMutex
功能——创建一个命名或匿名的互斥量对象
格式
HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCTSTR lpName);
参数说明
lpMutexAttributes——必须取值NULL。
bInitialOwner——指示当前线程是否马上拥有该互斥量(即马上加锁)。
lpName——互斥量名称。
(3) CreateSemaphore
功能——创建一个命名或匿名的信号量对象
格式
HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
LONG lInitialCount,
LONG lMaximumCount,
LPCTSTR lpName );
参数说明
lpSemaphoreAttributes——必须取值NULL。
lInitialCount——信号量的初始值。该值大于0,但小于lMaximumCount指定的最大值。
lMaximumCount——信号量的最大值。
lpName——信号量名称。
(4) WaitForSingleObject
功能——使程序处于等待状态,直到信号量hHandle出现(即其值大于等于1)或超过规定的等待时间
格式
DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);
参数说明
hHandle——信号量指针。
dwMilliseconds——等待的最长时间(INFINITE为无限等待)。
(5) ReleaseSemaphore
功能——对指定信号量加上一个指定大小的量。成功执行则返回非0值
格式
BOOL ReleaseSemaphore(HANDLE hSemaphore,
LONG lReleaseCount,
LPLONG lppreviousCount );
参数说明
hSemaphore——信号量指针。
lReleaseCount——信号量的增量。
lppreviousCount——保存信号量当前值。
(6) ReleaseMutex
功能——打开互斥锁,即把互斥量加1。成功调用则返回0
格式
BOOL ReleaseMutex(HANDLE hMutex);
参数说明
hMutex——互斥量指针。
(7) InitializeCriticalSection
功能——初始化临界区对象
格式
VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
参数说明
lpCriticalSection——指向临界区对象的指针。
(8) EnterCriticalSection
功能——等待指定临界区对象的所有权
格式
VOID enterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
参数说明
lpCriticalSection——指向临界区对象的指针。
(9) LeaveCriticalSection
功能——释放指定临界区对象的所有权
格式
VOID LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
参数说明
lpCriticalSection——指向临界区对象的指针。
二、实验内容
1.以下程序是模拟售票功能,使用临界区对象,其中SellPro_1,SellPro_2两个函数分别对应两个售票进程,一次售出一张票。
(1)阅读程序,了解CRITICAL_SECTION的使用方法,
在这里插入代码片
```//模拟售票程序
#include <windows.h>
#include <iostream>
using namespace std;
DWORD WINAPI SellPro_1( LPVOID lpParameter);
DWORD WINAPI SellPro_2( LPVOID lpParameter );
int tickets=100;
CRITICAL_SECTION critical_sec; //定义关键区域
void main()
{
HANDLE hThread1;
HANDLE hThread2;
InitializeCriticalSection(&critical_sec); //初始化关键区域
hThread1=CreateThread(NULL,0,SellPro_1,NULL,0,NULL);
hThread2=CreateThread(NULL,0,SellPro_2,NULL,0,NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);
getchar();//Sleep(4000);
}
DWORD WINAPI SellPro_1( LPVOID lpParameter )
{
while(TRUE)
{
Sleep(1);
EnterCriticalSection(&critical_sec); //进入关键代码区域
if(tickets>0)
{
cout<<"thread1 sell ticket : "<<--tickets<<endl;
}
else
break;
LeaveCriticalSection(&critical_sec); //离开代码关键区域
}
return 0;
}
DWORD WINAPI SellPro_2( LPVOID lpParameter)
{
while(TRUE)
{
Sleep(1);
EnterCriticalSection(&critical_sec); //进入关键代码区域
if(tickets>0)
{
cout<<"thread2 sell ticket : "<<--tickets<<endl;
}
else
break;
LeaveCriticalSection(&critical_sec); //离开代码关键区域
}
return 0;
}
```bash
在这里插入代码片
DWORD:
1、代表 unsigned long,一般用于返回值不会有负数的情况。
2、typedef unsigned long DWORD;
关于DWORD使用中重要的一点。DWORD 现在表示 32bit 无符号整数,即使以后 Windows 升级到64位,DWORD 仍然是 32bit 无符号整数(也许以后的 long 不是32bit了,只需要重新定义一下 DWORD 就可以了)。对于那些直接和位数有关的整数,最好不用 int, long, short 之类的类型,因为这些类型的位数可能不确定(比如,在16位程序里,int 是16位的,在32位程序里,int 是32位的,谁知道在以后的64位程序里,int 是多少位,long 又是多少位)。用重新定义的类型就没有这方面的问题了,最多到时候修改一下定义就可以了,而不需要在程序里一行一行的查找
3、在实际情况中,DWORD会根据操作系统的不同,被定义成了不同的长度
4、C++中使用DWORD不用声明,但是要加头文件Windows.h。
(1)DWORD全称Double Word,是指注册表的键值,每个word为2个字节的长度,DWORD 双字即为4个字节,每个字节是8位,共32位。
(2)DWORD在Windows下经常用来保存地址(或者存放指针)。
在这里插入代码片
```1 //模拟售票程序
2 #include <windows.h>
3 #include <iostream>
4 using namespace std;
5
6 DWORD WINAPI SellPro_1( LPVOID lpParameter);
7 DWORD WINAPI SellPro_2( LPVOID lpParameter );
8
9 int tickets=100;
10 CRITICAL_SECTION critical_sec; //定义关键区域
11 void main()
12 {
13 HANDLE hThread1;
14 HANDLE hThread2;
15
16 InitializeCriticalSection(&critical_sec); //初始化关键区域
17 hThread1=CreateThread(NULL,0,SellPro_1,NULL,0,NULL);
18 hThread2=CreateThread(NULL,0,SellPro_2,NULL,0,NULL);
19 CloseHandle(hThread1);
20 CloseHandle(hThread2);
21 Sleep(4000);
22 }
23
24 DWORD WINAPI SellPro_1( LPVOID lpParameter )
25 {
26 while(TRUE)
27 {
28 Sleep(1);
29 EnterCriticalSection(&critical_sec); //进入关键代码区域
30 if(tickets>0)
31 {
32 cout<<"thread1 sell ticket : "<<--tickets<<endl;
33 }
34 else
35 break;
36 LeaveCriticalSection(&critical_sec); //离开代码关键区域
37 }
38 return 0;
39 }
40
41 DWORD WINAPI SellPro_2( LPVOID lpParameter)
42 {
43 while(TRUE)
44 {
45 Sleep(1);
46 EnterCriticalSection(&critical_sec); //进入关键代码区域
47 if(tickets>0)
48 {
49 cout<<"thread2 sell ticket : "<<--tickets<<endl;
50
51 }
52 else
53 break;
54 LeaveCriticalSection(&critical_sec); //离开代码关键区域
55 }
56 return 0;
57 }
```bash
在这里插入代码片
2.以下程序通过使用信号量对象模拟售票功能。
(1)阅读并运行程序,了解信号量对象的使用方法。
在这里插入代码片
#include
#include
using namespace std;
static HANDLE g_hSemaphore = INVALID_HANDLE_VALUE;
static int g_Count = 100;
DWORD WINAPI Thread_A(LPVOID lpParamter);
DWORD WINAPI Thread_B(LPVOID lpParamter);
int main(int argc, char** argv)
{
HANDLE threadA = INVALID_HANDLE_VALUE;
HANDLE threadB = INVALID_HANDLE_VALUE;
g_hSemaphore = CreateSemaphore(NULL, 1, 20, TEXT("semaphore"));
threadA = CreateThread(NULL, 0, Thread_A, NULL, 0, NULL);
threadB = CreateThread(NULL, 0, Thread_B, NULL, 0, NULL);
getchar(); //Sleep(5000);
CloseHandle(threadA);
CloseHandle(threadB);
return 0;
}
DWORD WINAPI Thread_A(LPVOID lpParamter)
{
long count;
while(1)
{
WaitForSingleObject(g_hSemaphore, INFINITE);
if(g_Count>0)
cout<<"thread_A:"<<g_Count--<<endl;
else
break;
ReleaseSemaphore(g_hSemaphore, 1,&count);
Sleep(10);
}
return 0;
}
DWORD WINAPI Thread_B(LPVOID lpParamter)
{
long count;
while(1)
{
WaitForSingleObject(g_hSemaphore, INFINITE);
if(g_Count>0)
cout<<"thread_B:"<<g_Count--<<endl;
else
break;
ReleaseSemaphore(g_hSemaphore, 1,&count);
Sleep(10);
}
return 0;
}
```bash
在这里插入代码片
(2)将函数Thread_B中的语句Sleep(10);改为Sleep(20);,再分析程序运行结果。
3.以下程序是模拟一个简单的生产者-消费者问题,只有一个缓冲区,存放一个整型数据。
(1)阅读并运行程序,了解生产者-消费者问题中,互斥与同步是如何控制的。了解程序中临界区对象g_cs,信号量g_hEventBufferEmpty和g_hEventBufferFull的作用是什么,如何实现互斥与同步的控制。
在这里插入代码片
#include
#include
#include
const int END_PRODUCE_NUMBER=20;
int g_Buffer;
CRITICAL_SECTION g_cs;
HANDLE g_hEventBufferEmpty,g_hEventBufferFull;
unsigned int __stdcall ProducerThreadFun(PVOID pM) //生产者进程
{
int i;
for (i=1;i<=END_PRODUCE_NUMBER;i++)
{
WaitForSingleObject(g_hEventBufferEmpty,INFINITE);
EnterCriticalSection(&g_cs);
g_Buffer=i;
printf("生产者将数据%d放入缓冲区\n",i);
LeaveCriticalSection(&g_cs);
SetEvent(g_hEventBufferFull);
Sleep(1000);
}
return 0;
}
unsigned int __stdcall ConsumerThreadFun(PVOID pM) //消费者进程
{
int flag=1;
while(flag)
{
WaitForSingleObject(g_hEventBufferFull,INFINITE);
EnterCriticalSection(&g_cs);
printf("消费者从缓冲区中取出数据%d\n",g_Buffer);
if(g_Buffer==END_PRODUCE_NUMBER)
flag=0;
LeaveCriticalSection(&g_cs);
SetEvent(g_hEventBufferEmpty);
Sleep(1000);
}
return 0;
}
int main()
{
HANDLE hThread[2];
printf("生产者消费者问题\n");
InitializeCriticalSection(&g_cs);
g_hEventBufferEmpty=CreateEvent(NULL,FALSE,TRUE,NULL);
g_hEventBufferFull=CreateEvent(NULL,FALSE,FALSE,NULL);
hThread[0]=(HANDLE)_beginthreadex(NULL,0,ProducerThreadFun,NULL,0,NULL);
hThread[1]=(HANDLE)_beginthreadex(NULL,0,ConsumerThreadFun,NULL,0,NULL);
WaitForMultipleObjects(2,hThread,TRUE,INFINITE);
CloseHandle(hThread[0]);
CloseHandle(hThread[1]);
CloseHandle(g_hEventBufferEmpty);
CloseHandle(g_hEventBufferFull);
DeleteCriticalSection(&g_cs);
return 0;
}
(2)修改程序,将每次产生的数据改为一个100之内的随机数。
#include
#include
#include
#include
#include
int END_PRODUCE_NUMBER=1;
int g_Buffer;
CRITICAL_SECTION g_cs;
HANDLE g_hEventBufferEmpty,g_hEventBufferFul
unsigned int __stdcall ProducerThreadFun(PVOID pM) //生产者进程
{
int i;
srand((unsigned int)time(NULL));
for (;;)
{
i=rand()%100;
WaitForSingleObject(g_hEventBufferEmpty,INFINITE);
EnterCriticalSection(&g_cs);
g_Buffer=i;
printf("生产者将数据%d放入缓冲区\n",i);
LeaveCriticalSection(&g_cs);
SetEvent(g_hEventBufferFull);
Sleep(1000);
END_PRODUCE_NUMBER ++;
if(END_PRODUCE_NUMBER==10)
break;
}
return 0;
}
unsigned int __stdcall ConsumerThreadFun(PVOIDpM) //消费者进程
{
int flag=1;
while(flag)
{
WaitForSingleObject(g_hEventBufferFull,INFINITE);
EnterCriticalSection(&g_cs);
printf("消费者从缓冲区中取出数据%d\n",g_Buffer);
if(g_Buffer==END_PRODUCE_NUMBER)
flag=0;
LeaveCriticalSection(&g_cs);
SetEvent(g_hEventBufferEmpty);
Sleep(1000);
if(END_PRODUCE_NUMBER==10)
break;
}
return 0;
}
int main()
{
HANDLE
hThread[2];
printf("生产者消费者问题\n");
InitializeCriticalSection(&g_cs);
g_hEventBufferEmpty=CreateEvent(NULL,FALSE,TRUE,NULL);
g_hEventBufferFull=CreateEvent(NULL,FALSE,FALSE,NULL);
hThread[0]=(HANDLE)_beginthreadex(NULL,0,ProducerThreadFun,NULL,0,NULL);
hThread[1]=(HANDLE)_beginthreadex(NULL,0,ConsumerThreadFun,NULL,0,NULL);
WaitForMultipleObjects(2,hThread,TRUE,INFINITE);
CloseHandle(hThread[0]);
CloseHandle(hThread[1]);
CloseHandle(g_hEventBufferEmpty);
CloseHandle(g_hEventBufferFull);
DeleteCriticalSection(&g_cs);
return 0;
}
```bash
在这里插入代码片
(3)修改程序,生产者进程中产生数据的个数不固定为20,如果无键盘输入事件,则一直循环产生数据,直到有键盘按下的操作才停止。相应的消费者进程中语句也需要修改。
提示:可使用kbhit( )函数判断有无键盘输入。
在这里插入代码片
```#include
#include
#include
#include
#include
#include
int END_PRODUCE_NUMBER=1;
int g_Buffer;
CRITICAL_SECTION g_cs;
HANDLE g_hEventBufferEmpty,g_hEventBufferFull;
unsigned int __stdcall ProducerThreadFun(PVOID pM) //生产者进程
{
int i;
srand((unsigned int)time(NULL));
while(!kbhit())
{
i=rand()%100
WaitForSingleObject(g_hEventBufferEmpty,INFINITE);
EnterCriticalSection(&g_cs);
g_Buffer=i;
printf("生产者将数据%d放入缓冲区\n",i);
LeaveCriticalSection(&g_cs);
SetEvent(g_hEventBufferFull);
Sleep(1000);
END_PRODUCE_NUMBER ++;
}
return 0;
}
unsigned int __stdcall ConsumerThreadFun(PVOIDpM) //消费者进程
{
int flag=1;
while(flag)
{
WaitForSingleObject(g_hEventBufferFull,INFINITE);
EnterCriticalSection(&g_cs);
printf("消费者从缓冲区中取出数据%d\n",g_Buffer);
if(g_Buffer==END_PRODUCE_NUMBER)
flag=0;
LeaveCriticalSection(&g_cs);
SetEvent(g_hEventBufferEmpty);
Sleep(1000);
}
return 0;
}
int main()
{
HANDLEhThread[2];
printf("生产者消费者问题\n");
InitializeCriticalSection(&g_cs);
g_hEventBufferEmpty=CreateEvent(NULL,FALSE,TRUE,NULL);
g_hEventBufferFull=CreateEvent(NULL,FALSE,FALSE,NULL);
hThread[0]=(HANDLE)_beginthreadex(NULL,0,ProducerThreadFun,NULL,0,NULL);
hThread[1]=(HANDLE)_beginthreadex(NULL,0,ConsumerThreadFun,NULL,0,NULL);
WaitForMultipleObjects(2,hThread,TRUE,INFINITE);
CloseHandle(hThread[0]);
CloseHandle(hThread[1]);
CloseHandle(g_hEventBufferEmpty);
CloseHandle(g_hEventBufferFull);
DeleteCriticalSection(&g_cs);
return 0;
}
4.在理解简单生产者-消费者程序的基础上,自己编程实现稍复杂的模型,将缓冲区改为能够存放若干个整数的数组,如int g_Buffer[N]; 其中N为一个整型常量。一个生产者,两个消费者。算法流程参考教材。
#include
#include
#include
#include
const int END_PRODUCE_NUMBER=20;
int g_Buffer[100];
CRITICAL_SECTION g_cs;
HANDLE g_hEventBufferEmpty,g_hEventBufferFull;
unsigned int __stdcall ProducerThreadFun(PVOID pM) //生产者进程
{
int i=1;
while(!kbhit())
{
WaitForSingleObject(g_hEventBufferEmpty,INFINITE);
EnterCriticalSection(&g_cs);
g_Buffer[i]=i;
printf("生产者将数据%d放入缓冲区\n",i);
g_Buffer[++i]=i;
printf("生产者将数据%d放入缓冲区\n",i);
LeaveCriticalSection(&g_cs)
SetEvent(g_hEventBufferFull);
Sleep(1000);
i++;
}
return 0;
}
unsigned int __stdcall ConsumerThreadFun(PVOID pM) //消费者进程
{
int flag=1;
while(flag) {
static int i=1;
WaitForSingleObject(g_hEventBufferFull,INFINITE);
EnterCriticalSection(&g_cs);
printf("消费者从缓冲区中取出数据%d\n",i);
if(g_Buffer[i]==END_PRODUCE_NUMBER)
flag=0;
LeaveCriticalSection(&g_cs);
SetEvent(g_hEventBufferEmpty);
Sleep(1000);
i++;
}
return 0;
}
int main()
{
HANDLE hThread[2];
printf("生产者消费者问题\n");
InitializeCriticalSection(&g_cs);
g_hEventBufferEmpty=CreateEvent(NULL,FALSE,TRUE,NULL);
g_hEventBufferFull=CreateEvent(NULL,FALSE,FALSE,NULL);
hThread[0]=(HANDLE)_beginthreadex(NULL,0,ProducerThreadFun,NULL,0,NULL);
hThread[1]=(HANDLE)_beginthreadex(NULL,0,ConsumerThreadFun,NULL,0,NULL);
WaitForMultipleObjects(2,hThread,TRUE,INFINITE);
CloseHandle(hThread[0]);
CloseHandle(hThread[1]);
CloseHandle(g_hEventBufferEmpty);
CloseHandle(g_hEventBufferFull);
DeleteCriticalSection(&g_cs);
return 0;
}