第九节 线程的同步
一、事件
1、创建事件
HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName);
参数: lpEventAttributes:指向SECURITY_ATTRIBUTES结构体的指针,如果其值为NULL,则使用默认的安全性。
bManualReset:指定创建的是人工重置事件对象,还是自动重置事件对象。如果是人工重置对象,当线程等待到该对象的
所有权限之后,需要调用ResetEvent函数手动地将该事件设置为无信号状态;如果是自动重置事件对象,
当线程等到该对象的所有权之后,系统会自动将该对象设置为无信号状态。
bInitialState: 指定事件对象的初始状态,如果此参数值为真,那么该事件对象初始是有信号状态,否则是无信号状态。
lpName: 指定事件对象的名称,如果此参数为NULL,那么将创建一个匿名的事件对象。
2、设置事件对象状态
SetEvent函数将把指定的事件对象设置为有信号状态,该函数的原型如下:
BOOL SetEvent(HANDLE hEvent); 该参数指定将要设置其状态的事件对象的句柄。
3、重置事件对象状态
ReseEvent函数将把指定的事件对象设置为无信号状态,该函数的原型如下:
BOOL ResetEvent(HANDLE hEvent); 该参数指定将要重置其状态的事件对象句柄。如果成功返回非0.否则返回0.
4、使用事件对象实现线程同步
DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);
int tickets=100;
HANDLE g_hEvent;
void main()
{
HANDLE hThread1;
HANDLE hThread2;
g_hEvent=CreateEvent(NULL,FALSE,FALSE,NULL);
hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);
Sleep(4000);
CloseHandle(g_hEvent);
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
while(TRUE)
{
WaitForSingleObject(g_hEvent,INFINITE);
if(tickets>0)
{
Sleep(1);
cout<<"thread1 sell ticket:"<<tickets--<<endl;
SetEvent(g_hEvent);
}
else
{
SetEvent(g_hEvent);
break;
}
}
return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
while(TRUE)
{
WaitForSingleObject(g_hEvent,INFINITE);
if(tickets>0)
{
Sleep(1);
cout<<"thread1 sell ticket:"<<tickets--<<endl;
SetEvent(g_hEvent);
}
else
{
SetEvent(g_hEvent);
break;
}
}
return 0;
}
总结:当人工重置的事件对象得到通知时,等待该事件对象的所有线程均变成为可调度线程;当一个自动重置的事件对象得到通知
时,等待该事件对象的线程中只有一个线程变为可调度线程,同时操作系统会将该事件对象设置为无信号状态,这样,当对
所保护的代码执行完成后,需要调用SetEvent函数将该事件对象设置为有信号状态,而人工重置的事件对象,在一个线程得
到该事件对象之后,操作系统并不会将该事件对象设置为无信号状态,除非显式地调用ReseEvent函数将其设置为无信号状态,
否则该对象会一直是有信号状态。
5、保证应用程序只有一个实例运行
g_hEvent=CreateEvent(NULL,FALSE,FALSE,"tickets");
if(g_hEvent)
{
if(ERROR_ALREADY_EXISTS==GetLastError())
{
cout<<"only one instance can run!"<<endl;
return;
}
}
二、临界区
1、相关函数
void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
2、实例
DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);
int tickets=100;
CRITICAL_SECTION g_cs;
void main()
{
HANDLE hThread1;
HANDLE hThread2;
g_hEvent=CreateEvent(NULL,FALSE,FALSE,NULL);
hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);
InitializeCriticalSection(&g_cs);
Sleep(4000);
DeleteCriticalSection(&g_cs);
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
while(TRUE)
{
EnterCriticalSection(&g_cs);
Sleep(1);
if(tickets>0)
{
Sleep(1);
cout<<"thread1 sell ticket:"<<tickets--<<endl;
LeaveCriticalSection(&g_cs);
}
else
{
LeaveCriticalSection(&g_cs);
break;
}
}
return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
while(TRUE)
{
EnterCriticalSection(&g_cs);
Sleep(1);
if(tickets>0)
{
Sleep(1);
cout<<"thread1 sell ticket:"<<tickets--<<endl;
LeaveCriticalSection(&g_cs);
}
else
{
LeaveCriticalSection(&g_cs);
break;
}
}
return 0;
}
三、线程死锁
对多线程来说,人工线程1拥有了临界区对象A,等待临界区对象B的拥有权,线程2拥有了临界区对象B,等待临界区对象A的拥有权,
这九造成了死锁。
四、互斥,事件,临界的比较
1、互斥对象和事件对象都属于内核对象,利用内核对象进行线程同步时,速度较慢,但利用互斥对象和事件对象这样的内核对象,
可以在多进程中的各个线程间进行同步。
2、临界在用户方式下,同步速度较快,但是使用临界时,很容易进入死锁状态,因为在等待进入临界时无法设定超时值。
五、基于消息的异步套接字
1、相关函数说明
指定的套接字请求基于Windows消息的网络事件通知,并自动将该套接字设置为非阻塞模式。
int WSAAsyncSelect(SOCKET s,HWND hWnd,unsigned int wMsg, long lEvent);
参数: s: 标识请求网络事件通知的套接字描述符。
hWnd: 标识一个网络事件发生时接收消息的窗口的句柄。
wMsg: 指定网络事件发生时窗口将接收到的消息。
lEvent: 指定应用程序感兴趣的网络事件。

浙公网安备 33010602011771号