浅议Visual C++多线程设计

来源:百度文库 编辑:神马文学网 时间:2024/04/18 09:08:50
1 引言
随着的发展,Windows95 和WindowsNT 操作系统开始支持多任务的调度和处理。基于此,其所提供的多任务空间使程序员可以完全控制应用程序中细节的运行,从而编写高效率的应用程序。
多任务的实现基于两方面,即多和多线程。所谓进程是指在系统中正在运行的一个应用程序,而线程是系统分配处理器时间资源的基本单元,也可讲是进程之内独立执行的一个单元。利用多线程技术,在VC环境下使一个进程从主线程的执行开始进而创建一个或多个附加线程,达到多线程多任务的实现。
2 多线程及其设计思路
在Windows操作系统中,Win32应用程序可以在Windows平台上运行多个实例,每个应用程序实例都是一个独立的进程,而一个进程可以由不止一个线程来实现。与Win16应用程序的协同多任务方式不同,Win32应用程序组采取的是抢占式多任务方式,即同时执行多个进程和多个线程。线程具体讲是一个代码单元,在操作系统中运行是标志着代码运行流。
每个进程都有私有的虚拟地址空间,进程的所有线程共享同一地址空间。每个线程被分配一个时间片,一旦被激活,它正常运行直到时间片耗尽并被挂起,此时,操作系统选择另一个线程进行运行。通过时间片轮转,又出于各个时间片很小(20毫秒级),看起来就像多个线程同时在工作。实际上,只有在多处理器系统上才是真正的在可得到的处理器上同时运行多个线程。基于Win32的应用程序可以通过把给定进程分解(或创建)多个线程挖掘潜在的CPU时间,而且还可以加强应用程序,以使用户提高效率,加强反应能力以及进行后台辅助处理。
对于一个进程来说,当应用程序有几个任务要同时运行时,建立多个线程是有用的。如打印时,利用多线程机制实现多线程,就可在需要打印时创建一个负责完成打印功能的打印线程。创建打印线程之后,系统就变成了多线程。当进行打印时,CPU轮换着分配给这两个线程时间片,所以打印和其他功能一起同时在运行,这就充分利用了CPU处理打印工作之外的空闲时间片,并且避免了用户长久地等待打印时间。这就是所谓的由多线程来实现的多任务,在进行打印任务的同时又可以进行别的任务。
在VC中利用编程时,线程被分为工作者线程(Worker Thread)和用户界面线程(User Interface Thread)两大类。前者常用于处理后台任务,执行这些后台任务并不会耽搁用户对应用程序的使用,即用户操作无需等待后台任务的完成。后者常用来独立的处理用户输入和相应用户的事件。其中用户界面线程的特点是拥有单独的消息队列,可以具有自己的窗口界面,能够对用户输入和事件做出响应。在应用程序中,根据用户界面线程具有消息队列这一特点,可以使之循环等待某一事件发生后再进行处理。由于Windows95时抢先式多任务的操作系统,即使一个线程因等待某事件而阻塞,其他线程仍然可以继续执行。
3 多线程的调度和处理
在32 位 环境下,开发多线程应用程序可以利用提供的Win32 API 接口函数,也可以利用 中提供的类库进行开发。两种方式对于多线程编程原理是一样的,根据需要选择相应的工具。下面以利用MFC 类库实现多线程调度与处理为例,介绍多线程的实现方法以及多个线程间任务调度所应注意的一些关键技术。
3.1 基于MFC的多线程设计
在VC++6.0环境下,MFC类库提供了对多线程编程支持,使得多线程能方便的实现。MFC区分两种类型的线程:辅助线程(Worker Thread)和用户界面线程(UserInterface Thread)。辅助线程没有消息机制,通常用来执行后台计算和维护任务。MFC 为用户界面线程提供消息机制,用来处理用户的输入,响应用户产生的事件和消息。但对于Win32 的API 来说,这两种线程并没有区别,它只需要线程的启动地址以便启动线程执行任务。用户界面线程的一个典型应用就是类CWinApp,类CwinApp是CWinThread 类的派生类,应用程序的主线程是由它提供,并由它负责处理用户产生的事件和消息。
类CwinThread 是用户接口线程的基本类。CWinThread 的对象用以维护特定线程的局部数据。因为处理线程局部数据依赖于类CWinThread,所以所有使用MFC 的线程都必须由MFC 来创建。
3.2 多线程的创建及涉及的关键问题
要创建一个线程,需要调用函数AfxBeginThread。该函数通过参数重载可以实现辅助线程和用户界面线程的创建。但不论是辅助线程还是用户界面线程,都需要指定额外的参数以修改优先级,堆栈大小,创建标志和安全特性等。函数AfxBeginThread 返回指向CWinThread 类对象的指针。创建助手线程相对简单,并不必须从CWinThread 派生一个类。实现起来需要两步:实现控制函数和启动线程。
实现控制函数,定义线程。当进入该函数,线程启动;退出时,线程终止。该控制函数声明如下:
UINT MyControllingFunction( LPVOID pParam );
该参数是一个单精度32 位值。该参数接收的值将在线程对象创建时传递给构造函数。控制函数将用某种方式解释该值。可以是数量值,或是指向包括多个参数的结构的指针,甚至可以被忽略。如果该参数是指结构,则不仅可以将数据从调用函数传给线程,也可以从线程回传给调用函数。如果使用这样的结构回传数据,当结果准备好的时候,线程要通知调用函数。当函数结束时,应返回一个UINT 类型的值,通常,返回0表明成功,其它值则代表不同的错误。
控制函数建立后,接着就可以启动线程。由函数AfxBeginThread 创建并初始化一个CWinThread 类的对象,启动并返回该线程的地址,此时线程进入运行状态。下面是一个控制函数及其使用的实例代码。
UINT MyThreadProc( LPVOID pParam )
{
CMyObject* pObject = (CMyObject*)pParam;
if (pObject = = NULL || !pObject- >IsKindOf(RUNTIME_CLASS(CMyObject)))
return -1; //非法参数
…… //具体实现内容
return 0; //线程成功结束
}
//在程序中调用线程的函数
……
pNewObject = new CMyObject;
AfxBeginThread(MyThreadProc, pNewObject);
……
对于用户界面线程的创建有两种途径: 一是,首先从CWinTread 类派生一个类(注意必须要用宏DECLARE_DYNCREATE 和IMPLEMENT_DYNCREATE 对该类进行声明和实现),然后调用函数AfxBeginThread 创建CWinThread 派生类的对象进行初始化启动线程运行;二是,不调用函数AfxBeginThread,先通过构造函数创建类CWinThread 的一个对象,然后由程序员调用函数::CreateThread 来启动线程。通常类CWinThread 的对象在该线程的生存期结束时将自动终止,如果程序员希望自己来控制,则需要将m_bAutoDelete 设为FALSE。这样在线程终止之后类CWinThread 对象仍然存在,只是在这种情况下需要手动删除CWinThread 对象。
多线程创建后,就存在线程间优先级差异以及线程同步等问题。在Windows95 和WindowsNT 当中,每个任务都具有相应优先级,优先级共有32级,从0到31,系统按照不同的优先级调度线程的运行。 其中0~15 级是普通优先级,线程的优先级可以动态变化。高优先级线程优先运行,只有高优先级线程不运行时,才调度低优先级线程运行。优先级相同的线程按照时间片轮流运行;16~30 级是实时优先级,实时优先级与普通优先级的最大区别在于相同优先级的运行不按照时间片轮转,而是先运行的线程就先控制,如果它不主动放弃控制,同级或低优先级的线程就无法运行。
一个线程的优先级首先属于一个类,然后是其在该类中的相对位置。线程优先级可通过下式计算:
线程优先级= 进程类基本优先级+ 线程相对优先级
进程类的基本优先级有以下几种:
1) IDLE_PROCESS_CLASS
2) NORMAL_PROCESS_CLASS
3) HIGH_PROCESS_CLASS
4) REAL_TIME_PROCESS_CLASS
线程的相对优先级有:
1) THREAD_PRIORITY_IDLE
2) THREAD_PRIORITY_LOWEST
3) THREAD_PRIORITY_BELOW_NORMAL
4) THREAD_PRIORITY_NORMAL (缺省)
5) THREAD_PRIORITY_ABOVE_NORMAL
6) THREAD_PRIORITY_HIGHEST
7) THREAD_PRIORITY_CRITICAL
对于多线程应用程序,使用多线程技术其中最重要的问题就是要保证线程之间的资源同步访问。因为多个线程在共享资源时如果发生访问冲突,使得数据混乱,导致错误结果。
解决同步问题的一个简单的方法就是将同步类融入共享类当中,通常我们把这样的共享类称为线程安全类。MFC提供了一组同步和同步访问类来解决同步问题。同步对象有:CSyncObject, CSemaphore, CMutex, CcriticalSection和CEvent;同步访问对象包括CMultiLock和CSingleLock。同步类用于当访问资源时保证资源的整体性。其中CsyncObject是其它四个同步类的基类,不直接使用。信号同步类CSemaphore通常用于当一个应用程序中同时有多个线程访问一个资源的情况;事件同步类CEvent通常用于在应用程序访问资源之前应用程序必须等待的情况;而对于互斥同步类CMutex和临界区同步类CcriticalSection 都是用于保证一个资源一次只能有一个线程访问,二者的不同之处在于前者允许有多个应用程序使用该资源而后者则不允许对同一个资源的访问超出进程的范畴,而且使用临界区的方式效率比较高。CMultiLock和CSingleLock的区别仅在于是需要控制访问多个还是单个资源对象。
例如设计一个线程安全类,首先根据具体情况在类中加入同步类作为数据成员。在例子当中,可以将一个CSemaphore类的数据成员加入视窗类中,一个CCriticalSection类数据成员加入连接列表类,而一个CEvent数据成员加入类中。然后,在使用共享资源的函数当中,将同步类与同步访问类的一个锁对象联系起来。即,在资源的成员函数中应该创建一个CSingleLock 或CMultiLock 的对象并调用该对象的Lock 函数。当访问结束之后,调用UnLock 函数,释放资源。用这种方式来设计线程安全类比较容易。在保证线程安全的同时,省去了维护同步代码的麻烦,这也正是OOP 的思想。但是,使用线程安全类方法编程比不考虑线程安全要复杂,尤其体现在程序调试过程中,而且线程安全编程还会损失一部分效率,占用部分系统资源。
4 结束语
Win32环境下,多线程技术解决了许多程序设计中的实际需求,多线程技术与中断技术可称为是并驾齐驱,都是中重要的关键技术。在进行多线程设计时要注意线程同步、线程之间、线程与进程之间通信以及利用全局变量通信、利用用户定义消息通信、利用事件通信等等许多重要问题。
应用VC++6.0进行程序开发,设计多线程必须以各项实际任务的需求为依据。多线程技术的使用虽然解决了多任务的实现,但同时是以分配系统资源为基础,在应用中需避免线程资源的浪费,尽量保证程序运行的效率。