参考: 秒杀多线程面试题系列
案例-多线程资源访问冲突:
#include "stdafx.h"
#include "windows.h"
int number = 1;
unsigned long __stdcall ThreadProc1(void* lpParameter)
{
while (number < 100)
{
printf("线程1当前计数:%d\n",number);
number ++;
Sleep(100);
}
return 0;
}
unsigned long __stdcall ThreadProc2(void* lpParameter)
{
while (number < 100)
{
printf("线程2当前计数:%d\n",number);
number ++;
Sleep(100);
}
return 0;
}
int main(int argc, char* argv[])
{
HANDLE hThread1 = CreateThread(NULL,0,ThreadProc1,NULL,0,NULL);
HANDLE hThread2 = CreateThread(NULL,0,ThreadProc2,NULL,0,NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);
while (true)
{
;
}
return 0;
}
一、关键段CRITICAL_SECTION(临界区)
关键段CRITICAL_SECTION一共就四个函数,使用很是方便。下面是这四个函数的原型和使用说明。
函数功能:初始化
函数原型:
void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
函数说明:定义关键段变量后必须先初始化。
函数功能:销毁
函数原型:
void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
函数说明:用完之后记得销毁。
函数功能:进入关键区域
函数原型:
void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
函数说明:系统保证各线程互斥的进入关键区域。
函数功能:离开关关键区域
函数原型:
void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
关键段可以解决线程的互斥问题,但因为具有“线程所有权”,所以无法解决同步问题。
#include "stdafx.h"
#include "windows.h"
int number = 1;
CRITICAL_SECTION Critical;
unsigned long __stdcall ThreadProc1(void* lpParameter)
{
long count;
while (number < 100)
{
EnterCriticalSection(&Critical);
printf("线程1当前计数:%d\n",number);
number ++;
Sleep(100);
LeaveCriticalSection(&Critical);
}
return 0;
}
unsigned long __stdcall ThreadProc2(void* lpParameter)
{
long count;
while (number < 100)
{
EnterCriticalSection(&Critical);
printf("线程2当前计数:%d\n",number);
number ++;
Sleep(100);
LeaveCriticalSection(&Critical);
}
return 0;
}
int main(int argc, char* argv[])
{
InitializeCriticalSection(&Critical);
HANDLE hThread2 = CreateThread(NULL,0,ThreadProc2,NULL,0,NULL);
HANDLE hThread1 = CreateThread(NULL,0,ThreadProc1,NULL,0,NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);
while (true)
{
;
}
return 0;
}
二、事件Event
1. CreateEvent
函数功能:创建事件函数原型:
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset,
BOOL bInitialState,
LPCTSTR lpName
);
函数说明:
lpEventAttributes:表示安全控制,一般直接传入NULL。
bManualReset:确定事件是手动置位还是自动置位,传入TRUE表示手动置位,传入FALSE表示自动置位。如果为自动置位,则对该事件调用WaitForSingleObject()后会自动调用ResetEvent()使事件变成未触发状态。
bInitialState:表示事件的初始状态,传入TRUR表示已触发。
lpName:表示事件的名称,传入NULL表示匿名事件。
2. OpenEvent
函数功能:根据名称获得一个事件句柄。函数原型:
HANDLE OpenEvent(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpName //名称
);
函数说明:
dwDesiredAccess:表示访问权限,对事件一般传入EVENT_ALL_ACCESS。详细解释可以查看MSDN文档。
bInheritHandle:表示事件句柄继承性,一般传入TRUE即可。
lpName:表示名称,不同进程中的各线程可以通过名称来确保它们访问同一个事件。
3.SetEvent
函数功能:触发事件函数原型:BOOL SetEvent(HANDLE hEvent);
函数说明:每次触发后,必有一个或多个处于等待状态下的线程变成可调度状态。
4.ResetEvent
函数功能:将事件设为末触发函数原型:BOOL ResetEvent(HANDLE hEvent);
5.CloseHandle
BOOL CloseHandle(
HANDLE hObject
);
参数
hObject :代表一个已打开对象handle。
返回值
TRUE:执行成功;
FALSE:执行失败,可以调用GetLastError()获知失败原因。
由于事件是内核对象,因此使用CloseHandle()就可以完成清理与销毁了。
5.PulseEvent
函数功能:将事件触发后立即将事件设置为未触发,相当于触发一个事件脉冲。函数原型:BOOL PulseEvent(HANDLE hEvent);
函数说明:这是一个不常用的事件函数,此函数相当于SetEvent()后立即调用ResetEvent();此时情况可以分为两种:
1.对于手动置位事件,所有正处于等待状态下线程都变成可调度状态。
2.对于自动置位事件,所有正处于等待状态下线程只有一个变成可调度状态。
此后事件是末触发的。该函数不稳定,因为无法预知在调用PulseEvent ()时哪些线程正处于等待状态。
6.WaitForSingleObject
Dword WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);hHandle:等待对象的句柄
dwMilliseconds:等待的时间,单位为毫秒。INFINITE=一直等待
WAIT_ABANDONED: 指定对象是互斥对象,在线程被终止前,线程没有释放互斥对象。互斥对象的所属关系被授予调用线程,并且该互斥对象被置为非信号态。
WAIT_OBJECT_0: 指定对象的状态被置为信号状态。
WAIT_TIMEOUT: 超时,并且对象的状态为非信号态。
WAIT_FAILED: 调用失败。
#include "stdafx.h"
#include "windows.h"
int number = 1;
HANDLE hEvent;
unsigned long __stdcall ThreadProc1(void* lpParameter)
{
while (number < 100)
{
WaitForSingleObject(hEvent,INFINITE);
printf("线程1当前计数:%d\n",number);
number ++;
Sleep(100);
SetEvent(hEvent);
}
return 0;
}
unsigned long __stdcall ThreadProc2(void* lpParameter)
{
while (number < 100)
{
WaitForSingleObject(hEvent,INFINITE);
printf("线程2当前计数:%d\n",number);
number ++;
Sleep(100);
SetEvent(hEvent);
}
return 0;
}
int main(int argc, char* argv[])
{
HANDLE hThread1 = CreateThread(NULL,0,ThreadProc1,NULL,0,NULL);
HANDLE hThread2 = CreateThread(NULL,0,ThreadProc2,NULL,0,NULL);
hEvent = CreateEvent(NULL,FALSE,TRUE,"event");
CloseHandle(hThread1);
CloseHandle(hThread2);
while (true)
{
;
}
return 0;
}
三、互斥量Mutex
1. CreateMutex
函数功能:创建互斥量(注意与事件Event的创建函数对比)函数原型:
HANDLECreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCTSTR lpName
);
函数说明:
lpMutexAttributes:表示安全控制,一般直接传入NULL。
bInitialOwner:用来确定互斥量的初始拥有者。如果传入TRUE表示互斥量对象内部会记录创建它的线程的线程ID号并将递归计数设置为1,由于该线程ID非零,所以互斥量处于未触发状态。如果传入FALSE,那么互斥量对象内部的线程ID号将设置为NULL,递归计数设置为0,这意味互斥量不为任何线程占用,处于触发状态。
lpName:用来设置互斥量的名称,在多个进程中的线程就是通过名称来确保它们访问的是同一个互斥量。
函数访问值:
成功返回一个表示互斥量的句柄,失败返回NULL。
2. OpenMutex
函数原型:HANDLE OpenMutex(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpName //名称
);
函数说明:
dwDesiredAccess:表示访问权限,对互斥量一般传入MUTEX_ALL_ACCESS。详细解释可以查看MSDN文档。
bInheritHandle:表示互斥量句柄继承性,一般传入TRUE即可。
lpName:表示名称。某一个进程中的线程创建互斥量后,其它进程中的线程就可以通过这个函数来找到这个互斥量。
函数访问值:
成功返回一个表示互斥量的句柄,失败返回NULL。
3.ReleaseMutex
函数原型:BOOL ReleaseMutex (HANDLE hMutex)
函数说明:
访问互斥资源前应该要调用等待函数,结束访问时就要调用ReleaseMutex()来表示自己已经结束访问,其它线程可以开始访问。
#include "stdafx.h"
#include "windows.h"
int number = 1;
HANDLE hMutex;
unsigned long __stdcall ThreadProc1(void* lpParameter)
{
long count;
while (number < 100)
{
WaitForSingleObject(hMutex,INFINITE);
printf("线程1当前计数:%d\n",number);
number ++;
Sleep(100);
ReleaseMutex(hMutex);
}
return 0;
}
unsigned long __stdcall ThreadProc2(void* lpParameter)
{
long count;
while (number < 100)
{
WaitForSingleObject(hMutex,INFINITE);
printf("线程2当前计数:%d\n",number);
number ++;
Sleep(100);
ReleaseMutex(hMutex);
}
return 0;
}
int main(int argc, char* argv[])
{
HANDLE hThread2 = CreateThread(NULL,0,ThreadProc2,NULL,0,NULL);
HANDLE hThread1 = CreateThread(NULL,0,ThreadProc1,NULL,0,NULL);
hMutex = CreateMutex(NULL,false,"mutex");
CloseHandle(hThread1);
CloseHandle(hThread2);
while (true)
{
;
}
return 0;
}
四、信号量Semaphore
1. CreateSemaphore
函数功能:创建信号量函数原型:
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
LONG lInitialCount,
LONG lMaximumCount,
LPCTSTR lpName
);
函数说明:
lpSemaphoreAttributes:表示安全控制,一般直接传入NULL。
lInitialCount:表示初始资源数量。
lMaximumCount:表示最大并发数量。
lpName:表示信号量的名称,传入NULL表示匿名信号量。
2. OpenSemaphore
函数功能:打开信号量函数原型:
HANDLE OpenSemaphore(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpName
);
函数说明:
dwDesiredAccess:表示访问权限,对一般传入SEMAPHORE_ALL_ACCESS。详细解释可以查看MSDN文档。
bInheritHandle:表示信号量句柄继承性,一般传入TRUE即可。
lpName:表示名称,不同进程中的各线程可以通过名称来确保它们访问同一个信号量。
3. ReleaseSemaphore
函数功能:递增信号量的当前资源计数函数原型:
BOOL ReleaseSemaphore(
HANDLE hSemaphore,
LONG lReleaseCount,
LPLONG lpPreviousCount
);
函数说明:
hSemaphore:是信号量的句柄。
lReleaseCount:表示增加个数,必须大于0且不超过最大资源数量。
lpPreviousCount:可以用来传出先前的资源计数,设为NULL表示不需要传出。
注意:当前资源数量大于0,表示信号量处于触发,等于0表示资源已经耗尽故信号量处于末触发。在对信号量调用等待函数时,等待函数会检查信号量的当前资源计数,如果大于0(即信号量处于触发状态),减1后返回让调用线程继续执行。一个线程可以多次调用等待函数来减小信号量。
#include "stdafx.h"
#include "windows.h"
int number = 1;
HANDLE hDemaphore;
unsigned long __stdcall ThreadProc1(void* lpParameter)
{
long count;
while (number < 100)
{
WaitForSingleObject(hDemaphore,INFINITE);
printf("线程1当前计数:%d\n",number);
number ++;
Sleep(100);
ReleaseSemaphore(hDemaphore,1,&count);
}
return 0;
}
unsigned long __stdcall ThreadProc2(void* lpParameter)
{
long count;
while (number < 100)
{
WaitForSingleObject(hDemaphore,INFINITE);
printf("线程2当前计数:%d\n",number);
number ++;
Sleep(100);
ReleaseSemaphore(hDemaphore,1,&count);
}
return 0;
}
int main(int argc, char* argv[])
{
HANDLE hThread1 = CreateThread(NULL,0,ThreadProc1,NULL,0,NULL);
HANDLE hThread2 = CreateThread(NULL,0,ThreadProc2,NULL,0,NULL);
hDemaphore = CreateSemaphore(NULL,1,100,"sem");
CloseHandle(hThread1);
CloseHandle(hThread2);
while (true)
{
;
}
return 0;
}