用VC++5实现多线程
----多任务、多进程和多线程
----Windows95和WindowsNT操作系统支持多任务调度和处理,由此提供了多任务空间。程序员可控制应用程序中每一个片段的运行,从而编写高效率的应用程序。
----所谓多任务通常包括两大类:多进程和多线程。进程是指在系统中正在运行的一个应用程序;线程是系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元。对于操作系统而言,其调度单元是线程。一个进程至少包括一个线程,通常将该线程称为主线程。一个进程从主线程的执行开始进而创建一个或多个附加线程,这就是所谓基于多线程的多任务。
----开发多线程应用程序可利用32位Windows环境提供的Win32API接口函数,也可利用VC++中提供的MFC类库。多线程编程在这两种方式下原理是一样的,用户可以根据需要选择相应的工具。本文重点讲述用VC++5提供的MFC类库实现多线程调度与处理的方法,以及由线程多任务所引发的同步多任务特征,并给出一个实现多线程的例程。
----基于MFC的多线程编程
----1.MFC对多线程的支持
----MFC类库提供了多线程编程支持,使用户编程更加方便。重要的是,在多窗口线程情况下,MFC直接提供了用户接口线程的设计。
----MFC区分两种类型的线程:辅助线程(WorkerThread)和用户界面线程(UserInterfaceThread)。辅助线程没有消息机制,通常用来执行后台计算和维护任务。MFC为用户界面线程提供消息机制,用来处理用户的输入,响应用户产生的事件和消息。但对于Win32的API来说,这两种线程并没有区别,它只需要线程的启动地址以便启动线程执行任务。用户界面线程的一个典型应用就是CWinApp类,它是CWinThread类的派生类,提供应用程序的主线程,并负责处理用户产生的事件和消息。CWinThread类是用户接口线程的基本类,其对象用以维护特定线程的局部数据。因为处理线程局部数据依赖于CWinThread类,所以所有使用MFC的线程都必须由MFC来创建。例如,由run-time函数_beginthreadex创建的线程就不能使用任何MFCAPI。
----2.辅助线程和用户界面线程的创建和终止
----要创建一个线程,需要调用函数AfxBeginThread。该函数因参数重载不同而具有两种版本,分别对应辅助线程和用户界面线程。无论是辅助线程还是用户界面线程,都需要指定额外的参数以修改优先级、堆栈大小、创建标志和安全特性等。函数AfxBeginThread返回指向CWinThread类对象的指针。
----创建助手线程相对简单,只需实现控制函数和启动线程,而不必从CWinThread派生一个类。简要说明如下:
----(1)实现控制函数。控制函数定义该线程。当进入该函数,线程启动;退出时,线程终止。该控制函数声明如下:
----UINTMyControllingFunction(LPVOIDpParam);
----该参数是一个单精度32位值。该参数接收的值将在线程对象创建时传递给构造函数,控制函数将用某种方式解释该值。它可以是数量值,或是指向包括多个参数的结构的指针,甚至可以忽略。如果该参数是指结构,则不仅可以将数据从调用函数传给线程,也可以从线程回传给调用函数。如果使用这样的结构回传数据,当结果准备好时,线程要通知调用函数;当函数结束时,应返回一个UINT类型的值,指明结束的原因。通常,返回0表明成功,其他值分别代表不同的错误。
----(2)启动线程。由函数AfxBeginThread创建并初始化一个CWinThread类的对象,启动并返回该线程的地址,则线程进入运行状态。
----下面用简单的代码说明怎样定义一个控制函数以及如何在程序的其他部分使用。
UINTMyThreadProc(LPVOIDpParam)
{
CMyObject*pObject=(CMyObject*)pParam;
if(pObject==NULL||!pObject->IsKindOf(RUNTIME_CLASS(CMyObject)))
return-1;//非法参数
……//具体实现内容
return0;//线程成功结束
}
//在程序中调用线程的函数
……
pNewObject=newCMyObject;
AfxBeginThread(MyThreadProc,pNewObject);
……
----创建用户界面线程有两种方法。第一种方法,首先从CWinTread类派生一个类(注意:必须要用宏DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE对该类进行声明和实现);然后调用函数AfxBeginThread创建CWinThread派生类的对象进行初始化,启动线程运行。第二种方法,先通过构造函数创建类CWinThread的一个对象,然后由程序员调用函数::CreateThread来启动线程。通常CWinThread类的对象在该线程的生存期结束时将自动终止,如果程序员希望自己来控制,则需要将m_bAutoDelete设为FALSE。这样在线程终止之后,CWinThread类对象仍然存在,此时需要手动删除CWinThread对象。
----通常线程函数结束之后,线程将自行终止,CWinThread类将为我们完成结束线程的工作。如果在线程的执行过程中程序员希望强行终止线程的话,则需要在线程内部调用AfxEndThread(nExitCode),其参数为线程结束码。这样将终止线程的运行,并释放线程所占用的资源。如果从另一个线程来终止该线程,则必须在两个线程之间设置通信方法。如果从线程外部来终止线程,还可以使用Win32函数(CWinThread类不提供该成员函数):BOOLTerminateThread(HANDLEhThread,DWORDdwExitcode)。但在实际程序设计中对该函数的使用一定要谨慎,因为一旦该命令发出,将立即终止该线程,并不释放线程所占用的资源,这样可能会引起系统不稳定。
----如果所终止的线程是进程内的最后一个线程,则在该线程终止之后进程也相应终止。
----3.进程和线程的优先级
----在Windows95和WindowsNT操作系统中,任务是有优先级的,共有32级,从0到31,系统按照不同的优先级调度线程的运行。其中:
----(1)0~15级是普通优先级,线程的优先级可以动态变化。高优先级线程优先运行,只有高优先级线程不运行时,才调度低优先级线程运行。优先级相同的线程按照时间片轮流运行。
----(2)16~30级是实时优先级,实时优先级与普通优先级的最大区别在于,相同优先级进程的运行不按照时间片轮转,而是先运行的线程就先控制CPU,如果它不主动放弃控制,同级或低优先级的线程就无法运行。
----一个线程的优先级首先属于一个类,然后是其在该类中的相对位置。线程优先级的计算可以如下式表示:
----线程优先级=进程类基本优先级+线程相对优先级
----进程类的基本优先级有:
----IDLE_PROCESS_CLASS
----NORMAL_PROCESS_CLASS
----HIGH_PROCESS_CLASS
----REAL_TIME_PROCESS_CLASS
----线程的相对优先级有:
----THREAD_PRIORITY_IDLE
----(最低优先级,仅在系统空闲时执行)
----THREAD_PRIORITY_LOWEST
----THREAD_PRIORITY_BELOW_NORMAL
----THREAD_PRIORITY_NORMAL(缺省)
----THREAD_PRIORITY_ABOVE_NORMAL
----THREAD_PRIORITY_HIGHEST
----THREAD_PRIORITY_CRITICAL(非常高的优先级)
----4.线程同步
----编写多线程应用程序最重要的问题就是线程之间的资源同步访问,多个线程在共享资源时如果发生访问冲突,会产生不正确的结果。例如,一个线程正在更新一个结构的内容的同时,另一个线程正试图读取该结构。结果,我们将无法得知所读取的数据是什么状态。
----MFC提供了一组同步和同步访问类来解决这个问题。其中,同步对象包括:CSyncObject、CSemaphore、CMutex,CCriticalSection和CEvent;同步访问对象包括:CMultiLock和CSingleLock。
----同步类用于访问资源时保证资源的整体性。其中CSyncObject是其他四个同步类的基类,不直接使用。信号同步类CSemaphore通常用于当一个应用程序中同时有多个线程访问一个资源的情况(例如,应用程序允许对同一个Document有多个View);事件同步类CEvent通常用于在应用程序访问资源之前应用程序必须等待的情况(比如,在数据写进一个文件之前数据必须从通信端口得到);互斥同步类CMutex和临界区同步类CCriticalSection都是用于保证一个资源一次只能有一个线程访问,二者的不同之处在于前者允许有多个应用程序使用该资源,例如,该资源在一个DLL当中,而后者则不允许对同一个资源的访问超出进程的范畴,而且使用临界区的方式效率比较高。
----同步访问类用于获得对这些控制资源的访问。CMultiLock和CSingleLock的区别仅在于是需要控制访问多个还是单个资源对象。
----5.同步类的使用方法
----解决同步问题的一个简单方法是将同步类融入共享类中,通常我们把这样的共享类称为线程安全类。下面举例说明同步类的使用方法。比如,一个用以维护一个账户的连接列表的应用程序。该应用程序允许3个账户在不同的窗口中检测,但一次只能更新一个账户。当一个账户更新之后,需要将更新的数据通过网络传给一个数据文档。
----该例中将使用3种同步类。由于允许一次检测3个账户,可使用CSemaphore来限制对3个视窗对象的访问。当更新一个账目时,应用程序使用CCriticalSection来保证一次只有一个账目更新。在更新成功之后,发CEvent信号,该信号释放一个等待接收信号事件的线程,该线程将新数据传给数据文档。
----要设计一个线程安全类,首先根据具体情况在类中加入同步类作为数据成员。在本例中,可以将一个CSemaphore类的数据成员加入视窗类中,将一个CCriticalSection类数据成员加入连接列表类,而将一个CEvent数据成员加入数据存储类中。
----然后,在使用共享资源的函数中,将同步类与同步访问类的一个锁对象联系起来。即:在访问控制资源的成员函数中应该创建一个CSingleLock或CMultiLock的对象,并调用该对象的Lock函数。当访问结束之后,调用UnLock函数释放资源。
----用这种方式来设计线程安全类比较容易。在保证线程安全的同时,省去了维护同步代码的麻烦,这也正是OOP的思想。但是使用线程安全类方法编程比不考虑线程安全要复杂,尤其体现在程序调试过程中。而且线程安全编程还会损失一部分效率,比如在单CPU计算机中多个线程之间的切换会占用一部分资源。
----编程实例
----本文以VC++5中一个简单的基于对话框的MFC例程为例来说明实现多线程任务调度与处理的方法。
----该例程定义两个用户界面线程、一个显示线程(CDisplayThread)和一个计数线程(CCounterThread)。这两个线程同时操作一个字符串变量m_strNumber,其中显示线程将该字符串在一个列表框中显示,而计数线程则将该字符串中的整数加1。在例程中,可以分别调整进程、计数线程和显示线程的优先级。例程中的同步机制使用CMutex和CSingleLock来保证两个线程不能同时访问该字符串,同步机制执行与否将明显影响程序的执行结果。在该例程中允许把两个线程暂时挂起,以查看运行结果。例程中还允许查看计数线程的运行。该例程中所处理的问题也是多线程编程中非常具有典型意义的问题。
----该程序主要有三个用于调整优先级的组合框,分别用于选择同步机制、显示计数线程运行、挂起线程的复选框,以及显示运行结果的列表框。
----在本程序中使用了两个线程类CCounterThread和CDisplayThread,共同操作定义在CMutexesDlg中的字符串对象m_strNumber。本程序对同步类CMutex的使用方法就是按照本文所讲述的融入的方法来实现的。同步访问类CSingleLock的锁对象则在各线程的具体实现中定义。
----:程序的具体实现方法及代码发表在计算机世界WWW站点上,地址在http://www.computerworld.com.cn/98/skill/default.htm。
本文地址:http://com.8s8s.com/it/it3097.htm