- 临界区在使用时以CRITICAL_SECTION结构对象保护共享资源,并分别用EnterCriticalSection()和LeaveCriticalSection() 函数去标识和释放一个临界区。所用到的CRITICAL_SECTION结构对象必须经过InitializeCriticalSection()的初始化后才能使用,而且必须确保所有线程中的任何试图访问此共享资源的代码都处在此临界区的保护之下。否则临界区将不会起到应有的作用,共享资源依然有被破坏的可能。
简单来说,EnterCriticalSection没有给资源加锁,只是给线程加了锁,对于加了同一种锁的线程,只能依次执行,不许同步执行,但是对于不加锁或者加了不同锁的线程,可以同步执行。什么时候可以用到:线程不多时,全部为他们加同一种锁,使他们依次执行;或者加两种锁,两个方式同时执行
CRITICAL_SECTION 所使用的头文件<windows.h>
- HANDLE 一个32位的无符号整数,用作句柄;HANDLE类型可以为(Event,Mutex,Process,Thread,Semaphore)数组
- PVOID 一个普通指针类型等价于(void *)
- WaitForSingleObject的作用是等待一个指定的对象(如Mutex对象),直到该对象处于非占用的状态(如Mutex对象被释放)或超出设定的时间间隔;
WaitForSingleObject函数用来检测hHandle事件的信号状态,在某一线程中调用该函数时,线程暂时挂起,如果在挂起的dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回
当你创建一个线程时,其实那个线程是一个循环,在那个死循环里要找到合适的条件退出那个死循环,在Windows里往往是采用事件的方式,用事件的方式来通知从线程运行函数退出来,它的实现原理是这样,在那个死循环里不断地使用WaitForSingleObject函数来检查事件是否满足,如果满足就退出线程,不满足就继续运行
DWORD WINAPI WaitForSingleObject(
HANDLE hHandle,//可以指定一系列的对象,如Event、Job、Memory resource notification、Mutex、Process、Semaphore、Thread、Waitable timer等
DWORD dwMilliseconds//超时间隔
);
- CreateEvent是一个WindowsAPI函数,用来创建或打开一个命名的或无名的事件对象,如果函数调用成功,函数返回事件对象的句柄。
HANDLECreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,// 安全属性
BOOL bManualReset,// 复位方式
BOOL bInitialState,// 初始状态
LPCTSTR lpName // 对象名称
);
- 使用SetEvent函数可以将事件对象的状态置为有信号状态;使用ResetEvent函数将事件对象的状态置为无信号状态。当一个自动复原的事件对象的状态被置为有信号状态时,该对象状态将一直保持有信号状态,直至一个等待线程被释放;系统将自动将此函数置为无信号状态。如果没有等待线程正在等待,事件对象的状态将保持有信号状态。
- _beginthreadex(),process.h 中的函数,用来创建一个线程
unsigned long _beginthreadex(
void *security,//安全属性,为NULL时表示默认安全性
unsigned stack_size,//线程的堆栈大小,一般默认为0
unsigned(_stdcall *start_address)(void *), //所要启动的线程函数
void *argilist, // 线程函数的参数,是一个void*类型,传递多个参数时用结构体
unsigned initflag, //新线程的初始状态,0表示立即执行,CREATE_SUSPENDED表示创建之后挂起
unsigned *threaddr//用来接收线程ID
);
返回值:// 成功返回新线程句柄,失败返回0
- WaitForMultipleObjects是Windows中的一个功能非常强大的函数,几乎可以等待Windows中的所有的内核对象
DWORD WaitForMultipleObjects(
DWORD nCount, // 指定列表中的句柄数量,就是 Double Word, 每个word为2个字节的长度,DWORD双字即为4个字节,每个字节是8位
CONST HANDLE *lpHandles, //*lpHandles 句柄数组的指针,lpHandles为指定对象句柄组合中的第一个元素
BOOL fWaitAll, //等待的类型,如果为TRUE,表示除非对象都发出信号,否则就一直等待下去;如果FALSE,表示任何对象发出信号即可
DWORD dwMilliseconds //指定要等候的毫秒数。如设为零,表示立即返回;如指定常数INFINITE,则可根据实际情况无限等待下去
);
以下为生产者消费者具体实现代码,设置了一个生产者,一个消费者,一个缓冲区,最多放一个产品
#include<stdio.h>
#include<windows.h>
#include<process.h>
const int END_PRODUCE_NUMBER=10;//本次一共生产10个产品
int g_Buffer;//缓冲区,一次放一个产品,生产者与消费者只能互斥进行访问
CRITICAL_SECTION g_cs;//临界区在使用时以CRITICAL_SECTION结构对象保护共享资源
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);//由于g_hEventBufferEmpty初始化为TRUE,故会先执行,往空的缓冲区里放产品1,由于事件初始化为自动复原,故唤醒后将变为无信号状态
EnterCriticalSection(&g_cs);//在一个线程进行生产或消费时,其余线程不能再进行生产或消费等操作,即保持线程间的同步
g_Buffer=i;
printf("生产者将数据%d放入缓冲区\n",i);
LeaveCriticalSection(&g_cs);
SetEvent(g_hEventBufferFull);//SetEvent将事件对象的状态置为有信号状态,从而唤醒正在等待的消费者线程,由于事件初始化为自动复原,故唤醒后将变为无信号状态
}
return 0;
}
unsigned int __stdcall ConsumerThreadFun(PVOID pM)
{
volatile 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(10);
}
return 0;
}
int main()
{
HANDLE hThread[2];
printf("\t生产者消费者问题<一>\n");
InitializeCriticalSection(&g_cs);//所用到的CRITICAL_SECTION结构对象必须经过InitializeCriticalSection()的初始化后才能使用
g_hEventBufferEmpty=CreateEvent(NULL,FALSE,TRUE,NULL);//信号初始化为TRUE,并且为自动复原事件
g_hEventBufferFull=CreateEvent(NULL,FALSE,FALSE,NULL);//信号初始化为FALSE,并且为自动复原事件
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;
}
结果如图
- ReleaseSemaphore 功能:按指定数量增加指定信号量对象的计数
BOOL WINAPI ReleaseSemaphore(
_In_ HANDLE hSemaphore,
_In_ LONG lReleaseCount,//这个信号量对象在当前基础上所要增加的值,这个值必须大于0,如果信号量加上这个值会导致信号量的当前值大于信号量创建时指定的最大值,那么这个信号量的当前值不变,同时这个函数返回FALSE
_Out_opt_ LPLONG lpPreviousCount//指向返回信号量上次值的变量的指针,如果不需要信号量上次的值,那么这个参数可以设置为NULL
);
- CreateSemaphore 功能:创建一个新的信号量;如执行成功,返回信号量对象的句柄
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, //安全属性
LONG lInitialCount, //设置信号量的初始计数。可设置零到lMaximumCount之间的一个值
LONG lMaximumCount, //设置信号量的最大计数
LPCT STRlpName//指定信号量对象的名称
);
- 信号量的使用规则:
- 如果当前资源计数大于0,那么信号量处于触发状态;
- 如果当前资源计数等于0,那么信号量处于未触发状态;那么系统会让调用线程进入等待状态。CreateSemaphore(NULL,0,1,NULL); 当第二个参数为0时,调用线程就会进入等待状态
- 系统绝对不会让当前资源计数变为负数;
- 当前资源计数绝对不会大于最大资源计数。
- memset()函数:用来对一段内存空间全部设置为某个字符,一般用在对定义的字符串进行初始化为‘ ’或‘/0’
extern void *memset(
void *buffer, //指针或是数组
int c, //赋给buffer的值
int count//buffer的长度
);
以下是消费者生产者问题的代码,此次设置一个生产者线程,两个消费者线程,四个缓冲区
#include<stdio.h>
#include<process.h>
#include<windows.h>
#define END_PRODUCE_NUMBER 8
#define BUFFER_SIZE 4//本次设置4个缓冲区
int g_Buffer[BUFFER_SIZE];
int g_i,g_j;
CRITICAL_SECTION g_cs;
HANDLE g_hSemaphoreBufferEmpty,g_hSemaphoreBufferFull;
unsigned int __stdcall ProducerThreadFun(PVOID pM)
{
int i;
for(i=1;i<=END_PRODUCE_NUMBER;i++)
{
WaitForSingleObject(g_hSemaphoreBufferEmpty,INFINITE);
EnterCriticalSection(&g_cs);
g_Buffer[g_i]=i;
printf("生产者在缓冲池第%d个缓冲区中投放数据%d\n",g_i,g_Buffer[g_i]);
g_i=(g_i+1)%BUFFER_SIZE;
LeaveCriticalSection(&g_cs);
ReleaseSemaphore(g_hSemaphoreBufferFull,1,NULL);
}
printf("生产者完成任务,线程结束运行\n");
return 0;
}
unsigned int __stdcall ConsumerThreadFun(PVOID pM)
{
while(1)
{
WaitForSingleObject(g_hSemaphoreBufferFull,INFINITE);
EnterCriticalSection(&g_cs);
printf("编号为%d的消费者从缓冲池中第%d个缓冲区中取出数据%d\n",GetCurrentThreadId(),g_j,g_Buffer[g_j]);
if(g_Buffer[g_j]==END_PRODUCE_NUMBER)
{
LeaveCriticalSection(&g_cs);
ReleaseSemaphore(g_hSemaphoreBufferFull,1,NULL);
break;
}
g_j=(g_j+1)%BUFFER_SIZE;
LeaveCriticalSection(&g_cs);
Sleep(50);
ReleaseSemaphore(g_hSemaphoreBufferEmpty,1,NULL);
}
printf("编号为%d的消费者收到通知,线程结束运行\n",GetCurrentThreadId());
return 0;
}
int main()
{
HANDLE hThread[3];//本次设置三个线程,一个生产者,两个消费者
int i;
printf("\t生产者消费者问题<二>\n");
InitializeCriticalSection(&g_cs);
g_hSemaphoreBufferEmpty=CreateSemaphore(NULL,4,4,NULL);//初始化信号量,初始计数为4,用于记录空缓冲区的个数
g_hSemaphoreBufferFull=CreateSemaphore(NULL,0,4,NULL);//初始化信号量,初始计数为0,用于记录放了一个产品的缓冲区的个数
g_i=0;
g_j=0;
memset(g_Buffer,0,sizeof(g_Buffer));//把四个缓冲区全部初始化为0
hThread[0]=(HANDLE)_beginthreadex(NULL,0,ProducerThreadFun,NULL,0,NULL);
hThread[1]=(HANDLE)_beginthreadex(NULL,0,ConsumerThreadFun,NULL,0,NULL);
hThread[2]=(HANDLE)_beginthreadex(NULL,0,ConsumerThreadFun,NULL,0,NULL);
WaitForMultipleObjects(3,hThread,TRUE,INFINITE);
for(i=0;i<3;i++)
CloseHandle(hThread[i]);
CloseHandle(g_hSemaphoreBufferEmpty);
CloseHandle(g_hSemaphoreBufferFull);
DeleteCriticalSection(&g_cs);
return 0;
}
结果如下图: