
原始地址:
多任务,多进程和多线程
Windows 95和Windows NT操作系统支持多任务调度和处理,从而提供了多任务空间. 程序员可以控制应用程序中每个片段的操作,从而编写高效的应用程序.
所谓的多任务处理通常包括两类: 多进程和多线程. 进程是在系统中运行的应用程序. 线程是系统分配处理器时间资源的基本单位,或者是进程内独立执行的单位. 对于操作系统,其调度单元是线程. 进程包括至少一个线程,通常称为主线程. 进程从执行主线程开始,然后创建一个或多个其他线程. 这称为基于多线程的多任务处理.
开发多线程应用程序可以使用32位Windows环境提供的Win32API接口功能,也可以使用VC ++提供的MFC类库. 这两种模式下的多线程编程原理是相同的,用户可以根据需要选择相应的工具. 本文重点介绍使用VC ++ 5提供的MFC类库实现多线程调度和处理的方法,以及线程多任务导致的同步多任务的特点,并给出实现多线程的例程.
基于MFC的多线程编程
1.MFC支持多线程
MFC类库提供了多线程编程支持,使用户编程更加方便. 重要的是,对于多窗口线程,MFC直接提供用户界面线程的设计.
MFC区分两种线程: 工作者线程和用户界面线程. 工作线程没有消息机制,通常用于执行后台计算和维护任务. MFC为用户界面线程提供了一种消息机制,以处理用户输入并响应用户生成的事件和消息. 但是对于Win32 API,这两个线程之间没有什么区别,它只需要线程的起始地址即可启动该线程来执行任务. 用户界面线程的典型应用程序是CWinApp类,它是CWinThread类的派生类,提供应用程序的主线程,并负责处理用户生成的事件和消息. CWinThread类是用户界面线程的基本类,其对象用于维护特定线程的本地数据. 因为处理线程本地数据取决于CWinThread类,所以所有使用MFC的线程都必须由MFC创建. 例如,运行时函数_beginthreadex创建的线程不能使用任何MFCAPI.
2. 辅助线程和用户界面线程的创建和终止
要创建线程,需要调用函数AfxBeginThread. 由于不同的参数重载,此函数有两个版本,分别对应于辅助线程和用户界面线程. 无论是工作线程还是用户界面线程,都需要指定其他参数来修改优先级,堆栈大小,创建标志,安全性功能等. 函数AfxBeginThread返回一个指向CWinThread对象的指针.
创建助手线程相对简单. 您只需要实现控制功能和启动线程,而无需从CWinThread派生一个类. 简要说明如下:
(1)实现控制功能. 控制功能定义线程. 进入该功能时,线程启动;退出时,线程终止. 控制函数声明如下:

UINTMyControllingFunction(LPVOIDpParam);
此参数是单精度32位值. 创建线程对象时,此参数接收的值将传递给构造函数,并且控制函数将以某种方式解释该值. 它可以是数字值,也可以是指向包含多个参数的结构的指针,甚至可以忽略. 如果参数引用结构,则数据不仅可以从调用函数传递到线程,还可以从线程传递回调用函数. 如果使用这种结构返回数据,则在准备好结果后,线程应通知调用函数;函数结束时,应返回UINT类型值,指示终止原因. 通常,返回0表示成功,其他值表示不同的错误.
(2)启动线程. 函数AfxBeginThread创建并初始化CWinThread类的对象,启动并返回线程的地址,然后线程进入运行状态.
以下代码显示了如何定义控制功能以及如何在程序的其他部分中使用它.
UINT MyThreadProc(LPVOID pParam)
{
CMyObject * pObject =(CMyObject *)pParam;
if(pObject == NULL ||!pObject-> IsKindOf(RUNTIME_CLASS(CMyObject)))
返回-1; //非法参数
……//详细的实现内容
return0; //线程成功结束
}
//调用程序中线程的功能

...
pNewObject =新的CMyObject;
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操作系统中,任务具有优先级. 从0到31,共有32个级别. 系统调度线程以根据不同的优先级运行. 其中:
(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. 线程同步
编写多线程应用程序中最重要的问题是线程之间资源的同步访问. 如果多个线程共享对资源的访问,则访问冲突将导致错误的结果. 例如,当一个线程正在更新结构的内容时,另一线程正在尝试读取该结构. 结果ccriticalsection 使用,我们将不知道从什么状态读取数据.
MFC提供了一组同步和同步访问类来解决此问题. 其中,同步对象包括: CSyncObject,CSemaphore,CMutex,CCriticalSection和CEvent. 同步访问对象包括: CMultiLock和CSingleLock.
同步类用于在访问资源时确保资源的完整性. 其中,CSyncObject是其他四个同步类的基类,不能直接使用. 信号同步类CSemaphore通常在应用程序中有多个线程同时访问资源时使用(例如,应用程序允许在同一Document上使用多个View);事件同步类CEvent通常用于访问应用程序中的资源应用程序必须等待的情况(例如,在将数据写入文件之前必须从通信端口获取数据);互斥锁同步类CMutex和关键部分同步类CCriticalSection用于确保一次只能有一个线程可以访问资源. 两者之间的区别在于,前者允许多个应用程序使用该资源,例如,该资源为在DLL中,而后者不允许访问超出进程范围的相同资源,而是使用关键部分. 效率比较高.
使用同步访问类来访问这些控制资源. CMultiLock和CSingleLock之间的区别只是需要控制对多个或单个资源对象的访问.
5. 如何使用同步类
解决同步问题的一种简单方法是将同步类集成到共享类中. 通常,我们将此类共享类称为线程安全类. 下面说明了如何使用同步类. 例如,维护帐户连接列表的应用程序. 该应用程序允许在不同的窗口中检测到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来确保两个线程无法同时访问该字符串. 同步机制是否执行,显然会影响程序的执行结果. 此例程使您可以临时挂起两个线程以查看结果. 该例程还允许您查看计数线程的运行情况. 该例程处理的问题也是多线程编程中非常典型的问题.
该程序主要有三个用于调整优先级的组合框ccriticalsection 使用,用于选择同步机制,显示用于计数线程运行的复选框,暂停线程以及用于显示运行结果的列表框.
在此程序中,两个线程类CCounterThread和CDisplayThread用于联合操作CMutexesDlg中定义的字符串对象m_strNumber. 该程序对同步CMutex的使用是根据本文所述的集成方法实现的. 同步访问类CSingleLock的锁对象是在每个线程的特定实现中定义的.
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-148724-1.html
但如果战争爆发我会第一时间到战地医院去