进程同步与互斥:Windows环境

一、实验目的:
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
在这里插入代码片

进程同步与互斥:Windows环境_第1张图片
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
在这里插入代码片

进程同步与互斥:Windows环境_第2张图片
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);,再分析程序运行结果。
进程同步与互斥:Windows环境_第3张图片
进程同步与互斥:Windows环境_第4张图片
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;
}



你可能感兴趣的:(操作系统)