介绍了Windows多线程技术、多线程间通信、以及多线程通信程序设计的主要函数,然后在前面关键技术的基础上,采用VC++ 6.0 为开发平台, 实现了一个基于VC++的多线程局域网聊天程序。
在多任务Windows编程中,为了提高代码的时、空效率,广泛采取后台程序和前台程序,而前台任务及后台任务是通过多线程技术来实现,从而线程间的通信就变得极为重要。Visual C++对Windwos线程和消息的多任务机制提供了强有力的支持,并封装了大量的通信函数,给多线程通信程序编程带来了很大的便利。
1 多线程技术概述
多线程在Windows编程中是一个比较重要的概念。所谓多线程,即在同一个程序中可以同时执行多个任务。这意味着,编程者可以将整个任务分成几个线程,然后让操作系统同时执行,从而提高程序的执行效率。一般线程被分为两种:用户界面线程和工作线程(又称为后台线程)。
1.1 创建线程与终止线程
创建线程的过程可以分成两大步骤,首先编写线程函数,然后创建线程,并将其参数线程函数地址指向刚刚编写的线程函数。线程函数的退出,除了使用退出函数处理外,大都是随着程序的结束而退出。
1.2 线程同步
多线程提高了程序执行的效率,提高了编程的灵活性,但同时也引入了新的问题。线程之间经常要同时访问一些资源,因此对共享资源进行访问引起冲突是不可避免的。另外,在Windwos的消息驱动方式下,必须某个事件的发生了,才可以执行某个线程,为了解决上述问题,必须引入线程同步的概念。Win32 API提供了多种同步控制对象,包括临界区、互斥对象、信号量和事件。
2 多线程间通信
一般而言,应用程序中的一个次要线程总是为主线程执行特定的任务,这样,主线程和次要线程间必定有一个信息传递的渠道,也就是主线程和次要线程间要进行通信。这种线程间的通信不但是难以避免的,而且在多线程编程中也是复杂和频繁的。多线程间通信可以采用全局变量、自定义消息、事件对象通信。
1) 使用全局变量进行通信
由于属于同一个进程的各个线程共享操作系统分配该进程的资源,故解决线程间通信最简单的一种方法是使用全局变量。对于标准类型的全局变量,可以使用volatile修饰符,它告诉编译器无需对该变量作任何的优化,即无需将它放到一个寄存器中,并且该值可被外部改变。如果线程间所需传递的信息较复杂,可以定义一个结构,通过传递指向该结构的指针进行传递信息。
2) 使用自定义消息进行通信
可以在一个线程的执行函数中向另一个线程发送自定义的消息来达到通信的目的。一个线程向另外一个线程发送消息是通过操作系统实现的。利用Windows操作系统的消息驱动机制,当一个线程发出一条消息时,操作系统首先接收到该消息,然后把该消息转发给目标线程,接收消息的线程必须已经建立了消息循环。
3) 使用事件对象进行通信
在线程之间传送信号的一种比较复杂的方法是使用享件对象,在MFC中有一个事件类CEvent。一个事件对象可以处于两种状态的一种:信号态和非信号态。线程能够监视处于信号态的事件,以便在适当的时间执行对事件的操作。
3 多线程通信程序设计
3.1 重要函数
1) Win32 API对多线程编程的支持
Win32提供了一系列的API函数来完成线程的创建、挂起、恢复、终结以及通信等工作。下面将选取其中的一些重要函数进行说明。
①HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,DWORD dwStackSize,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId);
该函数在其调用进程的进程空间里创建一个新的线程,并返回已建线程的句柄,如果创建成功则返回线程的句柄,否则返回NULL。
②DWORD SuspendThread(HANDLE hThread);
该函数用于挂起指定的线程,如果函数执行成功,则线程的执行被终止。
③DWORD ResumeThread(HANDLE hThread);
该函数用于结束线程的挂起状态,执行线程。
④VOID ExitThread(DWORD dwExitCode);
该函数用于线程终结自身的执行,主要在线程的执行函数中被调用。其中参数dwExitCode用来设置线程的退出码。
⑤BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode);
一般情况下,线程运行结束之后,线程函数正常返回,但是应用程序可以调用TerminateThread强行终止某一线程的执行。使用TerminateThread()终止某个线程的执行是不安全的,可能会引起系统不稳定;虽然该函数立即终止线程的执行,但并不释放线程所占用的资源。因此,一般不建议使用该函数。
⑥BOOL PostThreadMessage(DWORD idThread,UINT Msg,WPARAM wParam,LPARAM lParam);
该函数将一条消息放入到指定线程的消息队列中,并且不等到消息被该线程处理时便返回。调用该函数时,如果即将接收消息的线程没有创建消息循环,则该函数执行失败。
2) MFC对多线程编程的支持
MFC中有两类线程,分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。
工作者线程没有消息机制,通常用来执行后台计算和维护任务,如冗长的计算过程,打印机的后台打印等。用户界面线程一般用于处理独立于其他线程执行之外的用户输入,响应用户及系统所产生的事件和消息等。但对于Win32的API编程而言,这两种线程是没有区别的,它们都只需线程的启动地址即可启动线程来执行任务。
在MFC中,一般用全局函数AfxBeginThread()来创建并初始化一个线程的运行,该函数有两种重载形式,分别用于创建工作者线程和用户界面线程。两种重载函数原型和参数分别说明如下:
①CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc,LPVOID pParam,
nPriority=THREAD_PRIORITY_NORMAL, UINT nStackSize=0,
DWORD dwCreateFlags=0,LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);
②CWinThread* AfxBeginThread(CRuntimeClass* pThreadClass,
int nPriority=THREAD_PRIORITY_NORMAL,UINT nStackSize=0,
DWORD dwCreateFlags=0, LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);
一般情况下,调用AfxBeginThread()来一次性地创建并启动一个线程,但是也可以通过两步法来创建线程:首先创建CWinThread类的一个对象,然后调用该对象的成员函数CreateThread()来启动该线程。
③virtual BOOL CWinThread::InitInstance();
重载该函数以控制用户界面线程实例的初始化。初始化成功则返回非0值,否则返回0。用户界面线程经常重载该函数,工作者线程一般不使用InitInstance()。
④virtual int CWinThread::ExitInstance();
在线程终结前重载该函数进行一些必要的清理工作。该函数返回线程的退出码,0表示执行成功,非0值用来标识各种错误。同InitInstance()成员函数一样,该函数也只适用于用户界面线程。
3.2 VC++下多线程局域网聊天程序
聊天程序是现代生活中不可缺少的通信工具,为了在局域网中实现点对点的实时通信,在VC++的编程环境中,利用套接字库技术和多线程编程技术实现聊天程序。
1) 界面设计。界面设计如图1所示。
2) 函数
void CMyQQDlg:: SendMsg() //发送消息
void CMyQQDlg:: SendFile() //发送文件
void CMyQQDlg:: Caputer() //抓取屏幕
void CMyQQDlg::AddMsgList(CString IP,CString str) //添加信息于LISTBOX 控件中
CString CMyQQDlg::GetError(DWORD error) //返回错误信息
int CMyQQDlg::GetLocalHostName(CString &sHostName) //获得本地计算机名称
int CMyQQDlg::GetIpAddress(const CString &sHostName, CString &sIpAddress)//获得本地IP
int CMyQQDlg::GetNamebyAddress(const CString &IpAddress,CString &sYouName)//获得对方计算机名称
3) 线程启动
pThreadLisen=::AfxBeginThread(_ListenTcpThread,this); //开始TCP线程
pThreadLisen=::AfxBeginThread(_UDPThread,this); //开始UDP线程
4) 服务器线程
UINT _ListenTcpThread(LPVOID lparam) //TCP监听线程
UINT _UDPThread(LPVOID lparam) //UDP接受信息线程开始
5) 客户端线程
UINT _SendFileThread(LPVOID lparam) //发送文件线程
UINT _SendMsgThread(LPVOID lparam) //TCP发送信息线程
UINT _SendMsgUdpThread(LPVOID lparam) //UDP发送信息
UINT _ThreadCapture(LPVOID lparam) //抓取对方屏幕线程
以上程序在Windows XP, Microsoft Visual C++6.0中编译执行通过。
4 结束语
要设计出性能较好的多线程通信程序,不仅需要对操作系统的处理过程很清楚,还需要对VC++的线程处理函数十分了解,并对具体应用中各线程的关系非常清楚,以利于在应用程序中控制同步事件的发生,避免出现死锁或不能同步处理的现象。
作者:杨红军 王金英 来源:电脑知识与技术·学术交流 2008年29期
中国论文网(www.lunwen.net.cn)免费学术期刊论文发表,目录,论文查重入口,本科毕业论文怎么写,职称论文范文,论文摘要,论文文献资料,毕业论文格式,论文检测降重服务。