通过通知操作的方式来保持线程的同步,还可以方便实现对多个线程的优先级比较的操作
事件对象和互斥对象一样都属于内核对象,它包含一个使用计数,一个用于标识该事件是一个自动
重置还是一个人工重置的布尔值,和另一个用于指定该事件处于已通知状态还是未通知状态的布尔
值。由上面所述,可见事件对象可分为两种,一种是人工重置的,另一种是自动重置的。
当人工重置的事件得到通知时,等待该时间的所有线程均变为可调度线程,因为创建的人工重置对象 开始
时处于有信号状态,除非手动 ResetEvent为无信号状态(If TRUE, then you must use the ResetEvent
function to manually reset the state to nonsignaled. )而当一个自动事件得到通知时,等待该时间的所有线程
中只有一个线程变为可调度线程,因为当一个 线程申请到事件对象时,操作系统就会将它转换为无信号状
态
用人工重置的方法无法完成线程同步:
因为在申请事件对象的过程中Thread1和Thread2是按照时间片轮转执行的,他们都能申请到事件对 象,
其后的代码都得到保护,两个线程同时访问一个资源,因此运行结果是未知的,同步失败
下面是用自动重置的方法实现线程同步:
#include<windows.h>
#include<iostream.h>
DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);
HANDLE g_hEvent;
int tickets=100;
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,NULL);//创建自动重置事件对象
SetEvent(g_hEvent);//把它设置为有信号状态
Sleep(4000);
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
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)
{
while(TRUE)
{
WaitForSingleObject(g_hEvent,INFINITE);//保护下面的代码
if(tickets>0)
{
Sleep(1);
cout<<"thread2 sell ticket:"<<tickets--<<endl;
}
else
break;
SetEvent(g_hEvent);
}
return 0;
}
2.通过创建命名事件对象来实现只允许单个程序运行
g_hEvent=CreateEvent(NULL,FALSE,FALSE,"tickets");
if(g_hEvent)
{
if(ERROR_ALREADY_EXISTS==GetLastError())
{
cout<<"only instance can run!"<<endl;
return;
}
}
3.用临界区对象(关键代码段)实现线程同步
CRITICAL_SECTION g_cs;//创建临界区对象
InitializeCriticalSection(&g_cs);//初始化临界区对象
EnterCriticalSection(&g_cs);//获取临界区对象的所有权
。。。。。。。。。。//中间是被保护的代码
LeaveCriticalSection(&g_cs);//释放临界区对象的所有权
DeleteCriticalSection(&g_cs);//删除临界区度喜爱那个
4.线程死锁(线程同步过程中要避免的问题)
哲学家进餐的问题
线程1拥有了临界区对象A,等待临界区对象B的拥有权,线程2拥有了临界区对象B,等待临界区对象A的拥 有权,
就造成了死锁
5.互斥对象、事件对象与关键代码段的比较
互斥对象和事件对象属于内核对象,利用内核对象进行线程同步,速度较慢,但利用互斥对象和事件对象 这样的内
核对象,可以在多个进程中的各个线程间进行同步。
关键代码段是工作在用户方式下,同步速度较快,但在使用关键代码段时,很容易进入死锁状态,因为在 等待进入
关键代码段时无法设定超时值。
实现线程同步时首选临界对象法,如果程序中要用到多个临界对象,就必
须采用互斥对象和事件对象来避免死锁。
深入了解多线程编程和线程同步推荐《Windows核心编程》
6.基于消息的异步套接字
同步(Sync)指发送方发出数据后,等收到接收方发回的响应,才发下一个数据包的通信方式,
异步(Async)方式指的是发送方不等接收方响应,便接着发下个数据包的通信方式。
阻塞套接字模式是指执行此套接字的网络调用时,直到成功才返回,否则一直阻塞在此网络调用上,比如调用
recv()函数读取网络缓冲区中的数据,如果没有数据到达,将一直挂在recv()这个函数调用上,直到读到一些数据,
此函数调用才返回。
非阻塞套接字模式是指执行此套接字的网络调用时,不管是否执行成功,都立即返回。比如调用recv()函数读
取网络缓冲区中数据,不管是否读到数据都立即返回,而不会一直挂在此函数调用上。
在实际Windows网络通信软件开发中,异步非阻塞套接字是用的最多的。平常所说的
C/S(客户端/服务器)结构的软件就是异步非阻塞模式的。
同步跟异步模式作用在客户端(发送请求消息一方),阻塞跟非阻塞模式作用在服务端
(处理接收消息的一端)。异步非阻塞模式就是客户端可以不停发送请求,服务端可以
边干自己的事边响应发过来的请求,客户端不必因为服务端没有回应而停止工作,服务
端也不会因为没有收到请求而一直阻塞。这种模式的完成主要依靠WSAAsyncSelect()提
供的消息机制下的网络事件选择来实现的,当使用它登记的网络事件发生时(比如客户
端向服务端发送了一条请求消息),Windows应用程序相应的窗口函数将收到一个消息
(消息中指示了发生的网络事件,以及与事件相关的一些信息),然后依据消息内容对
客户端进行响应。在这个过程中,服务端程序对客户端的响应变为基于消息的模式,而
不是基于过程,这样就避免了服务器端的阻塞。(上一节编写的网络聊天程序是在阻塞
模式下运行的,之所以没有发生阻塞,是因为应用程序采用了多线程技术,将接收信息
这个任务交给另外一个线程来完成,从而避免了主线程的阻塞)
当使用socket()或WSASocket()函数创建套接字时,默认都是阻塞的,而WSAAsyncSelect()会
将套接字设置为非阻塞模式。
7.用异步套接字实现网络聊天程序
BOOL CChatDlg::OnInitDialog()
{
InitSocket();
}
BOOL CChatDlg::InitSocket()
{
m_socket=WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,0);
if(INVALID_SOCKET==m_socket)
{
MessageBox("创建套接字失败!");
return FALSE;
}
SOCKADDR_IN addrSock;
addrSock.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addrSock.sin_family=AF_INET;
addrSock.sin_port=htons(6000);
if(SOCKET_ERROR==bind(m_socket,(SOCKADDR*)&addrSock,sizeof(SOCKADDR)))
{
MessageBox("绑定失败!");
return FALSE;
}
if(SOCKET_ERROR==WSAAsyncSelect(m_socket,m_hWnd,UM_SOCK,FD_READ))
{
MessageBox("注册网络读取事件失败!");
return FALSE;
}
return TRUE;
}
void CChatDlg::OnSock(WPARAM wParam,LPARAM lParam)
{
switch(LOWORD(lParam))
{
case FD_READ:
WSABUF wsabuf;
wsabuf.buf=new char[200];
wsabuf.len=200;
DWORD dwRead;
DWORD dwFlag=0;
SOCKADDR_IN addrFrom;
int len=sizeof(SOCKADDR);
CString str;
CString strTemp;
HOSTENT *pHost;
if(SOCKET_ERROR==WSARecvFrom(m_socket,&wsabuf,1,&dwRead,&dwFlag,
(SOCKADDR*)&addrFrom,&len,NULL,NULL))
{
MessageBox("接收数据失败!");
return;
}
pHost=gethostbyaddr((char*)&addrFrom.sin_addr.S_un.S_addr,4,AF_INET);
//str.Format("%s说 :%s",inet_ntoa(addrFrom.sin_addr),wsabuf.buf);
str.Format("%s说 :%s",pHost->h_name,wsabuf.buf);
str+="/r/n";
GetDlgItemText(IDC_EDIT_RECV,strTemp);
str+=strTemp;
SetDlgItemText(IDC_EDIT_RECV,str);
break;
}
}
void CChatDlg::OnBtnSend()
{
// TODO: Add your control notification handler code here
DWORD dwIP;
CString strSend;
WSABUF wsabuf;
DWORD dwSend;
int len;
CString strHostName;
SOCKADDR_IN addrTo;
HOSTENT* pHost;
if(GetDlgItemText(IDC_EDIT_HOSTNAME,strHostName),strHostName=="")
{
((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP);
addrTo.sin_addr.S_un.S_addr=htonl(dwIP);
}
else
{
pHost=gethostbyname(strHostName);
addrTo.sin_addr.S_un.S_addr=*((DWORD*)pHost->h_addr_list[0]);
}
addrTo.sin_family=AF_INET;
addrTo.sin_port=htons(6000);
GetDlgItemText(IDC_EDIT_SEND,strSend);
len=strSend.GetLength();
wsabuf.buf=strSend.GetBuffer(len);
wsabuf.len=len+1;
SetDlgItemText(IDC_EDIT_SEND,"");
if(SOCKET_ERROR==WSASendTo(m_socket,&wsabuf,1,&dwSend,0,
(SOCKADDR*)&addrTo,sizeof(SOCKADDR),NULL,NULL))
{
MessageBox("发送数据失败!");
return;
}
}
8.Windows下编写高性能网络程序要注意的几点:
Windows程序都是基于消息的,采用基于消息的异步选择机制可以增加网络程序的效率,Windows平
台下要想编写高性能应用程序除了解网络之外,还必须了解程序在Windows下的工作原理。调用网络
函数时要对返回值进行判断,编写网络程序时要仔细,多做实验多调试,只有这样才能编写出高性能
的网络程序。