第16章 线程同步与异步套接字编程
1.事件对象
事件对象与互斥对象一样也属于内核对象。事件对象有两种不同的类型:
①人工重置的事件对象:当人工重置的事件对象得到通知时,等待该事件对象的所有线程均变为可调度线程。当线程等待到该对象的所有权之后,需要显示地调用ResetEvent函数手动将该事件对象设为无信号状态;
②自动重置的事件对象:当一个自动重置的事件对象得到通知时,等待该事件对象的线程中只有一个线程变为可调度线程。当线程得到该对象的所有权之后,系统会自动将该对象设置为无信号状态。
为了实现线程间的同步,不应该使用人工重置的事件对象,而应该使用自动重置的事件对象。
代码分析:
#include <windows.h>
#include <iostream.h>
int ticket=100;
HANDLE g_hEvent;//保存事件对象句柄
DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);
void main()
{
HANDLE thread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
HANDLE thread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
CloseHandle(thread1);
CloseHandle(thread2);
//创建事件对象,可用命名事件对象来控制只运行一个实例
g_hEvent=CreateEvent(NULL,
FALSE, //TRUE人工重置,FALSE 自动重置
FALSE, //初始化状态,TURE信号状态,FALSE非信号状态
"tickets"); //事件对象命名,NULL表示匿名
if(g_hEvent)
{
if(ERROR_ALREADY_EXISTS == GetLastError())
{
cout<<"Only one instance can run!"<<endl;
return;
}
}
SetEvent(g_hEvent);//将事件设置为有信号状态
Sleep(4000);
CloseHandle(g_hEvent);
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
WaitForSingleObject(g_hEvent,INFINITE);
while(ticket)
{
cout<<"thread1 sells : "<<ticket--<<endl;
Sleep(1);
SetEvent(g_hEvent);
}
return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
WaitForSingleObject(g_hEvent,INFINITE);
while(ticket)
{
cout<<"thread2 sells : "<<ticket--<<endl;
Sleep(1);
SetEvent(g_hEvent);
}
return 0;
}
2.关键代码段
关键代码段也称为临界区,工作在用户方式下。它是指一个小代码段,在代码能够执行前,它必须独占对某些资源的访问权。通常把多线程中访问同一种资源的那部分代码当做关键代码段。
代码分析:
#include "windows.h"
#include "iostream.h"
int ticket=100;
//HANDLE g_hEvent;
DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);
CRITICAL_SECTION g_cs;//临界区对象
void main()
{
HANDLE thread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
HANDLE thread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
CloseHandle(thread1);
CloseHandle(thread2);
//初始化一个临界区对象
InitializeCriticalSection(&g_cs);
Sleep(4000);
// 释放一个没有被占有的临界区对象的所有资源
DeleteCriticalSection(&g_cs);
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
while(TRUE)
{
EnterCriticalSection(&g_cs);//等待临界区对象的所有权
//当调用线程赋予所有权进,本函数返回
// 如果一直没能等待到,那么导致线程暂停
if(ticket>0)
{
cout<<"thread1 sells : "<<ticket--<<endl;
Sleep(1);
}
else break;
LeaveCriticalSection(&g_cs);//释放指定临界区对象所有权
}
return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
while(TRUE)
{
EnterCriticalSection(&g_cs);
if(ticket>0)
{
cout<<"thread2 sells : "<<ticket--<<endl;
Sleep(1);
}
else break;
LeaveCriticalSection(&g_cs);
}
return 0;
}
死锁:如果线程1拥有了临界区对象A,等待临界区对象B的拥有权,线程二拥有了临界区对象B,等待临界区对象A的拥有权,这样就造成了死锁。
互斥对象、事件对象与关键代码段的比较:
1.互斥对象和事件对象都属于内核对象,利用内核对象进行线程同步时,速度较慢,但利用互斥对象和事件对象这样的内核对象,可以在多个进程中的各个线程间进行同步。
2.关键代码段工作在用户方式下,同步速度较快,但在使用关键代码段时,很容易进入死锁状态,因为在等待进入关键代码段时无法设定超时值。
基于消息的异步套接字:
Windows套接字在两种模式下执行IO操作:阻塞模式和非阻塞模式。Windows Sockets为了支持Windows消息驱动机制,使应用程序开发者能够方便地处理网络通信,它对网络事件采用了基于消息的异步存储策略,即采用非阻塞方式实现网络应用程序。异步选择函数WSAAsyncSelect提供了消息机制的网络事件选择,当使用它登记的网络事件发生时,Windows应用程序响应的窗口函数将收到一个消息,消息中指示了发生的网络事件以及一些相关信息。