在Windows操作系统中,每个进程可以同时执行多个线程,也就是说一个进程可以同时完成多个任务。为了同时执行多个线程,Windows采用了时间片的分配方式管理CPU,系统通过时间片调度轮流执行各个线程。对于像通信程序这样既耗费时间,有需要保持对用户输入响应的应用程序来说,应用多线程编程技术是较好地选择。
1、进程与线程
进程是应用程序的一次执行,而线程是隶属于进程的一个独立的可执行体,可以理解为进程中的一段程序片段,一个进程至少拥有一个代表进程执行体的主线程。
线程是操作系统分配CPU时间片的基本单元,操作系统为每个线程分配不同的CPU时间片,在某一个时刻,CPU只执行一个时间片内的线程,各线程轮流执行。但由于时间片很小,使用户感觉多个线程在并发执行。
2、线程的优先级
优先级的高低决定了线程竞争获取CPU时间片能力的高低,线程的优先级由线程所在进程的优先级和线程本身的优先级决定。线程的默认优先级为THREAD_PRIORITY_NORMAL,可以使用SetThreadPiority()函数设置线程的优先级。
通常情况下,对于处理用户输入的线程,高于默认优先级,以便使应用程序对用户的输入响应更快。对于耗费CPU时间较多的线程,最好使用低于默认优先级的级别,这样在需要时,该线程可以被阻塞。
由于普通的类成员函数不能作为线程入口函数,所以实例定义一个静态类成员函数作为线程函数,为了和窗口类共享数据,将窗口的指针作为参数传递给线程函数。
3、多线程的创建
MFC中有两类线程,分别称为工作者线程(Worker Threads)和用户界面线程(UI Threads)。二者的区别主要在于工作者线程没有消息队列和消息循环,而用户界面线程有自己的消息队列和消息循环。
MFC中工作者线程一般用户耗时计算,将耗时计算放在一个单独的线程中进行。
MFC中用户界面线程:当程序中需要出现两个窗口,而且两个窗口都需要实时处理数据时,就需要使用MFC用户界面线程。要创建一个用户界面线程,必须要做到以下几点。
(1)从CWinThread类派生,建立自己的用户界面线程类;
(2)建立窗口类,作为用户界面线程的主窗口。
(3)重载InitInstance()函数,并在其中注册自己的窗口类,创建线程主窗口。
(4)在InitInstance()函数的最后 return TRUE;以进入消息循环。
(5)启动用户界面函数,通常使用AfxBeginThread(RUNTIME_CLASS(线程类名))。
4、线程间通信
创建线程相对容易,但是管理好多线程应用程序并不简单。在多线程应用程序中,主线程不仅能够任意创建和启动线程,而且还能随时终止线程,各个线程间还必须有一种通信机制,使一个线程能够知道另外一个线程的状态,所有这些都需要进行线程间通信,
实现线程间通信有以下几种方法。
(1)使用全部变量。需要定义一些全局变量,通过其取值进行各个线程的协调,这种方式最为简单,但全局变量定义过多会使程序变得复杂,与C++的面向对象思想相违背。
(2)使用自定义消息。当线程的状态改变需要其他线程响应时,调用PostMessage()函数发送一个消息,由另外一个的消息响应函数进行处理。
(3)使用事件对象。这是Windows中线程间通信的常用方法。事件对象有两种状态:有信号状态和无信号状态。当事件对象的状态改变时,其他线程能察觉这种变化,然后进行相应操作。
在MFC中,事件对象是用CEvent类封装的。CEvent类有两个重要的函数:SetEvent()函数和ResetEvent()函数。前者使事件对象处于有信号状态,后者使事件对象处于无信号状态。
在线程中使用事件对象的方法是,创建一个CEvent类对象,然后在合适的地方将事件对象设置为有信号或无信号,对于其他线程,则调用WaitForSingleObject()函数。当事件处于无信号状态时,WaitForSingleObject()函数使该线程挂起,直到事件对象变成有信号或挂起事件超时为止。在挂起过程中,尽管该线程已经启动,但WaitForSingleObject()函数不消耗CPU时间。
注意:CEvent类需要包含头文件“afxmt.h”。