线程同步与异步套接字编程
1.事件对象
上一次介绍了利用互斥对象实现线程同步http://blog.csdn.net/walkerkalr/article/details/19510909。这次将继续介绍另两种线程同步的方法:事件对象和关键字代码段。
1.1事件对象
事件对象也属于内核对象,包括三个成员
1)使用计数
2)用于指明该事件是一个自动重置的事件还是一个人工重置的事件的布尔值
3)用于指明该事件处于已通知状态还是未通知状态的布尔值
事件对象有两种不同的类型:人工重置的事件对象和自动重置的事件对象。当人工重置的事件对象得到通知时,等待该事件的所有线程均变为可调度线程。当一个自动重置的事件对象得到通知时,等待该时间对象的线程只有一个线程变为可调度线程。
1.2创建事件对象
1.2.1相关函数:
HANDLE CreateEvent( //创建事件对象
LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset,
BOOL bInitialState,
LPTSTR lpName);
BOOL SetEvent(//设置事件对象状态
HANDLE hEvent );
BOOL ResetEvent( //重置事件对象状态
HANDLE hEvent );
1.2.1利用事件对象实现线程同步
#include <windows.h>
#include <iostream>
using namespace std;
DWORD WINAPI Fun1Proc(
LPVOID lpParameter // thread data
);
DWORD WINAPI Fun2Proc(
LPVOID lpParameter // thread data
);
int tickets=100;
HANDLE g_hEvent;
void main()
{
HANDLE hThread1;
HANDLE hThread2;
hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);
g_hEvent=CreateEvent(NULL,FALSE,FALSE,"tickets");
if(g_hEvent)
{
if(ERROR_ALREADY_EXISTS==GetLastError())
{
cout<<"only instance can run!"<<endl;
return;
}
}
Sleep(4000);
CloseHandle(g_hEvent);
}
DWORD WINAPI Fun1Proc(
LPVOID lpParameter // thread data
)
{
while(TRUE)
{
WaitForSingleObject(g_hEvent,INFINITE);
if(tickets>0)
{
Sleep(1);
cout<<"thread1 sell ticket : "<<tickets--<<endl;
}
else
break;
SetEvent(g_hEvent);
}
return 0;
}
DWORD WINAPI Fun2Proc(
LPVOID lpParameter // thread data
)
{
while(TRUE)
{
WaitForSingleObject(g_hEvent,INFINITE);
if(tickets>0)
{
Sleep(1);
cout<<"thread2 sell ticket : "<<tickets--<<endl;
}
else
break;
SetEvent(g_hEvent);
}
return 0;
}
为了实现线程间的同步不应该使用人工重置的事件对象,而应该使用自动重置的事件对象。
当人工重置的事件对象得到通知时,等待该事件对象的所有线程均变为可调度线程;当一个自动重置的事件对象得到通知时,等待该事件对象的线程中只有一个线程变为可调度线程,同时操作系统会将该事件设置无信号状态,这样,当对所保护的代码执行完成后,需要调用SetEvent函数将该事件对象设置为有信号状态。而人工重置的事件对象,在一个线程得到该事件对象之后,操作系统并不会将该事件对象设置为无信号状态,除非显式的调用ResetEvent函数将其设置为无信号状态,否则该对象会一直是有信号状态。
2.关键代码段(临界区)
关键代码段也成为临界区,工作在用户方式下,它是指一个小代码段,在代码能够执行前,他必须独占对某些资源的访问权。
2.1相关API函数
void InitializeCriticalSection(//初始化临界区
LPCRITICAL_SECTION lpCriticalSection );
void EnterCriticalSection( //进入临界区
LPCRITICAL_SECTION lpCriticalSection );
void LeaveCriticalSection( //离开临界区
LPCRITICAL_SECTION lpCriticalSection );
void DeleteCriticalSection( //销毁临界区
LPCRITICAL_SECTION lpCriticalSection );
2.2利用临界区实现线程同步
#include <windows.h>
#include <iostream>
using namespace std;
DWORD WINAPI Fun1Proc(
LPVOID lpParameter // thread data
);
DWORD WINAPI Fun2Proc(
LPVOID lpParameter // thread data
);
int tickets=100;
CRITICAL_SECTION g_cs;
void main()
{
HANDLE hThread1;
HANDLE hThread2;
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 // thread data
)
{
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 // thread data
)
{
while(TRUE)
{
EnterCriticalSection(&g_cs);
Sleep(1);
if(tickets>0)
{
Sleep(1);
cout<<"thread2 sell ticket : "<<tickets--<<endl;
LeaveCriticalSection(&g_cs);
}
else{
LeaveCriticalSection(&g_cs);
break;
}
}
return 0;
}
3.线程死锁
对多线程来说,如果线程1拥有临界区对象A,等待临界区对象B的拥有权,线程2拥有临界区对象B,等待临界区对象A的拥有权,这就造成了死锁。
4.互斥对象、时间对象和关键代码段的比较
区别:
1)互斥对象和时间对象都属于内核对象,利用内核对象进行线程同步时,速度慢,但利用互斥对象和事件对象这样的内核对象,可以在多个进程中的各个线程间进行同步。
2)关键代码段工作在用户方式下,同步速度较快,但在使用关键代码段时,很容易进入死锁状态,因为等待进入关键代码段时无法设定超时值。
5.基于消息的异步套接字
windows套接字在两种模式下执行I/O操作:阻塞模式和非阻塞模式。在阻塞模式下,在I/O操作完成前,执行操作的Winsock函数会一直等待下去,不会理解返回;在非阻塞模式下,Winsock函数无论如何都会立即返回,在该函数执行的操作完成之后,系统会采用某种方式将操作结果通知给调用线程,后者根据通知信息可以判断该操作使正常完成了,还是出现错误了。
相关函数说明:
int WSAAsyncSelect(//选择网络事件通知windows消息
SOCKET s,
HWND hWnd,
unsigned int wMsg,
long lEvent
);
int WSAEnumProtocols(//获取网络协议相关信息
LPINT lpiProtocols,
LPWSAPROTOCOL_INFO lpProtocolBuffer,
ILPDWORD lpdwBufferLength
);
int WSAStartup(//初始化WS2_32.DLL的使用
WORD wVersionRequested,
LPWSADATA lpWSAData
);
int WSACleanup (void);//终止WS2_32.DLL使用
SOCKET WSASocket(//创建套接字
int af,
int type,
int protocol,
LPWSAPROTOCOL_INFO lpProtocolInfo,
GROUP g,
DWORD dwFlags
);
int WSARecvFrom(//接受消息
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesRecvd,
LPDWORD lpFlags,
struct sockaddr FAR *lpFrom,
LPINT lpFromlen,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
int WSASendTo(//发送消息
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesSent,
DWORD dwFlags,
const struct sockaddr FAR *lpTo,
int iToLen,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
struct hostent FAR *gethostbyname(//根据主机名获取IP
const char FAR *name
);
struct HOSTENT FAR * gethostbyaddr(//根据IP获取主机名
const char FAR *addr,
int len,
int type
);
==参考VC++深入详解
==转载请注明出处,谢谢!
线程同步与异步套接字编程
原文:http://blog.csdn.net/walkerkalr/article/details/19622851