mfc 线程.docx
- 文档编号:6659171
- 上传时间:2023-01-08
- 格式:DOCX
- 页数:20
- 大小:113.64KB
mfc 线程.docx
《mfc 线程.docx》由会员分享,可在线阅读,更多相关《mfc 线程.docx(20页珍藏版)》请在冰豆网上搜索。
mfc线程
线程机制
1.线程的概念
一般来说,我们把正在计算机中执行的程序叫做“进程”(Process),而不将其称为程序(Program)。
而线程(thread)则是“进程”中运行的一个子任务,也被称为轻量级进程(lightweightprocesses)。
线程是进程中的实体,一个进程可以拥有多个同时运行的线程,一个线程必须有一个父进程。
线程不拥有系统资源,只有运行所必须的一些数据结构;它与父进程的其它线程共享该进程所拥有的全部资源。
线程可以创建和撤消线程,从而实现程序的并发执行。
一般情况下,线程具有就绪、暂停(阻塞,挂起)和运行三种基本状态。
在多处理器的系统里,不同线程可以同时在不同的处理器上运行,甚至当它们属于同一个进程时也是如此。
大多数支持多处理器的操作系统都提供编程接口来让进程可以控制自己的线程与各处理器之间的关联度。
有时候,线程也被称作轻量级进程,因为启动一个线程所需的资源开销要比启动一个进程少很多。
就象进程一样,线程在程序中是独立的、并发的执行路径,每个线程有它自己的堆栈、自己的程序计数器和自己的局部变量。
但是,与进程之间资源相互隔离相比,进程中的线程之间资源的隔离程度要小些。
同一个进程中的线程之间互相共享内存、文件句柄和其它一些进程的状态。
进程可以支持多个线程同时运行,它们看似同时执行,但互相之间并不同步。
一个进程中的多个线程共享相同的内存地址空间,这就意味着它们可以访问相同的变量和对象,而且它们从同一个堆中分配内存空间。
尽管这让线程之间共享信息变得更容易,但是编程时必须小心,确保它们不会妨碍同一进程里的其它线程。
在Windows操作系统中,把线程视为系统中任务执行的基本单位。
2.线程的状态
在.NetFramework中处理线程的类库是System:
:
Threading,System:
:
Threading:
:
Thread:
:
ThreadState属性定义了线程执行时的状态。
线程从创建到终止,它一定处于其中某一个状态中。
具体的状态有以下几个:
Aborted:
线程处于Stopped状态中。
AbortRequested:
已对线程调用了Thread:
:
Abort方法,但线程尚未收到试图终止它的挂起的System:
:
Threading:
:
ThreadAbortException。
Background:
线程正作为后台线程执行(相对于前台线程而言)。
此状态可以通过设置Thread.IsBackground属性来控制。
Running:
线程已启动,它未被阻塞,并且没有挂起的ThreadAbortException。
Stopped:
线程已停止。
StopRequested:
正在请求线程停止。
这仅用于内部。
Suspended:
线程已挂起。
SuspendRequested:
正在请求线程挂起。
Unstarted:
尚未对线程调用Thread.Start方法。
WaitSleepJoin:
由于调用Wait、Sleep或Join,线程已被阻塞。
当线程被创建时,它处在Unstarted状态,Thread类的Start()方法将使线程状态变为Running状态,线程将一直处于这样的状态,除非我们调用了相应的方法使其挂起、阻塞、销毁或者自然终止。
如果线程被挂起,它将处于Suspended状态,除非我们调用resume()方法使其恢复执行,这时候线程将重新变为Running状态。
一旦线程被销毁或者终止,线程处于Stopped状态。
处于这个状态的线程将不复存在,正如线程开始启动后,将不可能回到Unstarted状态一样。
线程还有一个Background状态,它表明线程运行在前台还是后台。
在一个确定的时间,线程可能处于多个状态。
举例来说,一个线程被调用了Sleep而处于阻塞状态,而接着另外一个线程调用这个阻塞线程的Abort方法,这时候线程将同时处于WaitSleepJoin和AbortRequested状态。
3.线程的优先级
System:
:
Threading:
:
Thread:
:
Priority决定了线程的优先级别,从而决定了线程能够得到多少CPU时间。
高优先级的线程通常会比一般优先级的线程得到更多的CPU时间,如果不止一个高优先级的线程,操作系统将在这些线程之间循环分配CPU时间。
低优先级的线程得到的CPU时间相对较少,当没有高优先级的线程时,操作系统将挑选下一个低优先级的线程执行。
一旦低优先级的线程在执行时遇到了高优先级的线程,它将让出CPU资源给高优先级的线程。
新创建的线程优先级为一般优先级,我们可以设置线程的优先级别的值,它有五档级别,如下面所示:
●Highest
●AboveNormal
●Normal
●BelowNormal
●Lowest
4.线程的创建和管理
在.NetFramework里面,微软提供了一个库System:
:
Threading用来完成线程的创建,运行和终止。
System:
:
Threading里面有如下几个函数可以用来管理线程:
ThreadStart():
启动线程的执行;
Thread:
:
Suspend():
挂起线程,如果线程已挂起,则不起作用;
Thread:
:
Resume():
继续运行已挂起的线程;
Thread:
:
Interrupt():
中止处于Wait、Sleep或者Join线程状态的线程;
Thread:
:
Join():
阻止调用线程,直到某个线程终止时为止;
Thread:
:
Sleep():
将当前线程暂停指定的毫秒数;
Thread:
:
Abort():
终止此线程。
创建一个新线程是非常容易的,可以通过以下的语句来创建一个新的线程:
Threadthread=gcnewThread(gcnewThreadStart(ThreadFunc));
Thread->Start();
第一条语句创建一个新的Thread对象,并指明了一个该线程的方法。
当新的线程开始时,该方法也就被调用执行了。
该线程对象通过一个System:
:
Threading:
:
ThreadStart类的一个实例来调用它要调用的线程方法。
第二条语句正式开始该新线程,一旦方法Start()被调用,该线程就保持在一个"alive"的状态下了,你可以通过读取它的IsAlive属性来判断它是否处于"alive"状态。
下面的语句显示了如果一个线程处于"alive"状态下就将该线程挂起的方法:
if(thread.IsAlive){
thread->Suspend();
}
不过要注意,线程对象的Start()方法只是启动了该线程,而并不保证其线程方法ThreadFunc()能立即得到执行。
它只是保证该线程对象能被分配到CPU时间,而实际的执行还要由操作系统根据空闲的处理器时间来决定。
一个线程的方法不包含任何参数,同时也不返回任何值。
它的命名规则和一般函数的命名规则相同。
它既可以是静态的(static)也可以是非静态的(nonstatic)。
当它执行完毕后,相应的线程也就结束了,其线程对象的IsAlive属性也就被置为false了。
下面是一个线程方法的实例:
voidThreadFunc()
{
for(inti=0;i<10;i++){
Console:
:
WriteLine("ThreadFunc{0}",i);
}
}
5.一个演示示例
下面来看一个实际的演示程序:
#include"stdafx.h"
usingnamespaceSystem;
usingnamespaceSystem:
:
Text;
usingnamespaceSystem:
:
IO;
usingnamespaceSystem:
:
Threading;
refclassWork
{
public:
intData;
voidDoJob()
{
for(inti=0;i<10;i++){
Console:
:
WriteLine("ThreadAisrunning......");
Thread:
:
Sleep(1000);
}
}
voidDoJob2()
{
for(inti=0;i<10;i++){
Console:
:
WriteLine("ThreadBisrunning......");
Thread:
:
Sleep(1000);
}
}
};
intmain(array : String^>^args) { Console: : WriteLine("Serverprogramstarted"); Work^w=gcnewWork; //创建一个新的线程 ThreadStart^threadDelegate=gcnewThreadStart(w,&Work: : DoJob); Thread^newThread=gcnewThread(threadDelegate); newThread->Start(); //创建一个新的线程 ThreadStart^threadDelegate2=gcnewThreadStart(w,&Work: : DoJob2); Thread^newThread2=gcnewThread(threadDelegate2); newThread2->Start(); Console: : WriteLine("Pressenterkeytoquit..."); Console: : ReadLine(); return0; } 程序的运行结果如下,可以看到,由于两个线程是同时在运行的,因此输出结果中两个线程显示的字符串是交替显示的。 Serverprogramstarted Pressenterkeytoquit... ThreadBisrunning...... ThreadAisrunning...... ThreadBisrunning...... ThreadAisrunning...... ThreadBisrunning...... ThreadAisrunning...... ThreadBisrunning...... ThreadAisrunning...... ThreadBisrunning...... ThreadAisrunning...... ThreadBisrunning...... ThreadAisrunning...... ThreadBisrunning...... ThreadAisrunning...... ThreadBisrunning...... ThreadAisrunning...... ThreadBisrunning...... ThreadAisrunning...... ThreadBisrunning...... ThreadAisrunning...... 6.前台线程和后台线程 .NetFramework的公用语言运行引擎(CommonLanguageRuntime,CLR)能区分两种不同类型的线程: 前台线程和后台线程。 这两者的区别就是: 应用程序必须运行完所有的前台线程才可以退出;而对于后台线程,应用程序则可以不考虑其是否已经运行完毕而直接退出,所有的后台线程在应用程序退出时都会自动结束。 一个线程是前台线程还是后台线程可由它的IsBackground属性来决定。 这个属性是可读可写的。 它的默认值为false,即意味着一个线程默认为前台线程。 我们可以将它的IsBackground属性设置为true,从而使之成为一个后台线程。 下面的例子是一个命令行程序,程序一开始便启动了10个线程,每个线程运行5秒钟时间。 由于线程的IsBackground属性默认为false,即它们都是前台线程,所以尽管程序的主线程很快就运行结束了,但程序要到所有已启动的线程都运行完毕才会结束。 示例代码如下: usingSystem; usingSystem: : Threading; voidThreadFunc() { DateTime^start=DateTime: : Now; while((DateTime: : Now-start)->Seconds<5) ; } voidMain() { for(inti=0;i<10;i++){ Thread^thread=gcnewThread(gcnewThreadStart(ThreadFunc)); Thread->Start(); } } 接下来我们对上面的代码进行略微修改,将每个线程的IsBackground属性都设置为true,则每个线程都是后台线程了。 那么只要程序的主线程结束了,整个程序也就结束了。 示例代码如下: usingSystem; usingSystem: : Threading; voidThreadFunc() { DateTime^start=DateTime: : Now; while((DateTime: : Now-start)->Seconds<5); } voidMain() { for(inti=0;i<10;i++){ //创建一个新的线程 Thread^thread=newThread(newThreadStart(ThreadFunc)); //设置为后台线程 Thread->IsBackground=true; Thread->Start(); } } 既然前台线程和后台线程有这种差别,那么我们怎么知道该如何设置一个线程的IsBackground属性呢? 下面是一些基本的原则: 对于一些在后台运行的线程,当程序结束时这些线程没有必要继续运行了,那么这些线程就应该设置为后台线程。 比如一个程序启动了一个进行大量运算的线程,可是只要程序一旦结束,那个线程就失去了继续存在的意义,那么那个线程就应作为后台线程。 而对于一些服务于用户界面的线程往往要设置为前台线程,因为即使程序的主线程结束了,其他它用户界面的线程很可能要继续存在来显示相关的信息,所以不能立即终止它们。 7.线程优先级 一旦一个线程开始运行,线程调度程序就可以控制其所获得的CPU时间。 如果一个托管的应用程序运行在Windows机器上,则线程调度程序是.Net框架的一部分。 通过设置线程的优先级可以使该线程获得不同的CPU时间。 线程的优先级是由Thread.Priority属性控制的,其值包含: ThreadPriority.Highest、ThreadPriority.AboveNormal、ThreadPriority.Normal、ThreadPriority.BelowNormal和ThreadPriority.Lowest。 从它们的名称上我们自然可以知道它们的优先程度,所以这里就不多作介绍了。 线程的默认优先级为ThreadPriority.Normal。 理论上,具有相同优先级的线程会获得相同的CPU时间,不过在实际执行时,消息队列中的线程阻塞或是操作系统的优先级的提高等原因会导致具有相同优先级的线程会获得不同的CPU时间。 不过从总体上来考虑仍可以忽略这种差异。 你可以通过以下的方法来改变一个线程的优先级。 thread.Priority=ThreadPriority.AboveNormal; 或是: thread.Priority=ThreadPriority.BelowNormal; 通过上面的第一句语句你可以提高一个线程的优先级,那么该线程就会相应的获得更多的CPU时间;通过第二句语句你降低了那个线程的优先级,于是它就会被分配到比原来少的CPU时间了。 你可以在一个线程开始运行前或是在它的运行过程中的任何时候改变它的优先级。 理论上你可以任意的设置每个线程的优先级,不过一个优先级过高的线程往往会影响到其他它程的运行,甚至影响到系统中其他它序的运行,所以最好不要随意的设置线程的优先级。 8.挂起线程和恢复线程 Thread类分别提供了两个方法来挂起线程和恢复执行线程: 函数Thread.Suspend能暂停一个正在运行的线程,而Thread.Resume又能让那个线程继续运行。 不像Windows内核,.NetFramework是不记录线程的挂起次数的,所以不管你挂起线程过几次,只要调用一次Thread.Resume就可以让挂起的线程重新开始运行。 Thread类还提供了一个静态的Thread.Sleep方法,它能使一个线程自动的挂起一定的时间,然后自动的重新开始运行。 一个线程能在它自身内部调用Thread.Sleep方法,也能在自身内部调用Thread.Suspend方法,可是一定要别的线程来调用它的Thread.Resume方法才可以重新开始。 下面的例子显示了如何运用Thread.Sleep方法: while(i<100){ DoWork(); //挂起线程 Thread: : Sleep(5000); } 9.终止线程 你可以通过以下的语句在一个线程中将另一个线程终止掉: Thread: : Abort(); 下面我们来解释一下Abort()方法是如何工作的。 因为公用语言执行引擎管理了所有的托管的线程,它能在每个线程内抛出异常。 Abort()方法能在目标线程中抛出一个ThreadAbortException异常从而导致目标线程的终止。 不过Abort()方法被调用后,目标线程可能并不是马上就终止了。 因为只要目标线程正在调用非托管的代码,而且还没有返回的话,该线程就不会立即终止。 而如果目标线程在调用非托管的代码而且陷入了一个死循环的话,该目标线程就根本不会终止。 不过这种情况只是一个特例,更多的情况是目标线程在调用托管的代码,一旦Abort()被调用那么该线程就立即被终止了。 在实际应用中,一个线程终止了另一个线程后,往往要等那个线程完全终止了它才可以继续运行,这样的话我们就应该用到Join()方法。 示例代码如下: thread: : Abort();//要求终止另一个线程 thread: : Join();//直到另一个线程完全终止了,它才继续运行 但是如果另一个线程一直不能终止的话(比如说遇到了上面所说的原因),我们就需要给Join()方法设置一个时间限制,方法如下: Thread: : Join(5000);//暂停5秒 这样,在5秒后,不管那个线程有没有完全终止,本线程就强行运行了。 该方法还返回一个布尔型的值,如果是true则表明那个线程已经完全终止了,而如果是false的话,则表明已经超过了时间限制了。 10.时钟线程 .NetFramework中的Timer类提供时钟线程,它包含在System: : Threading名字空间中,它的作用就是在一定的时间间隔后调用一个线程的方法。 下面展示一个具体的实例,该实例以1秒为时间间隔,在控制台中输出不同的字符串,代码如下: usingnamespaceSystem; usingnamespaceSystem: : Threading; boolTickNext=true; voidTickTock(Object^stateInfo) { Console: : WriteLine(TickNext? "Tick": "Tock"); TickNext=! TickNext; } voidmain() { Console: : WriteLine("PressEntertoterminate..."); Timer^timer=gcnewTimer(gcnewTimerCallback(TickTock),nullptr,1000,1000); Console: : ReadLine(); } 从上面的代码中,我们知道第一个函数回调是在1000毫秒后才发生的,以后的函数回调也是在每隔1000毫秒之后发生的,这是由Timer对象的构造函数中的第三个参数所决定的。 程序会在1000毫秒的时间间隔后不断的产生新线程,直到用户按下回车键才结束运行。 不过值得注意的是,虽然我们设置了时间间隔为1000毫秒,但是实际运行的时候往往并不能非常精确。 因为Windows操作系统并不是一个实时系统,而公用语言解释引擎也不是实时的,所以由于线程调度的千变万化,实际的运行效果往往不能精确到毫秒级,但是对于一般的应用来说这已经是足够的了。 在MFC中,线程分为用户界面线程和工作者线程,其创建的过程有所不同,下面分别进行介绍。 1.用户界面线程的创建 2.工作者线程 当一个线程终止时,关闭该线程所有属性的所有对象句柄,一般来说线程的终止包括正常终止和异常终止。 正常终止是控制函数到达函数中的结束点,该线程即终止,对于工作者线程来说也可以利用函数AfxEndThread();来终止线程,其函数原型如下: voidAfxEndThread(UINTnExitCode); 参数nExitCode表示线程的退出码。 14.3.3线程间通信 重要的问题,什么是线程的通信问题呢? 举个例子来说,在一个应用程序中有两个线程,一个线程负责接收用户的输入,另一个负责数据统计,那么负责数据统计的线程所使用的数据是接收线程的数据,因此这就属于线程间的通信问题。 程序设计中的任何通信方式都可以用于线程通信之中,例如: 发送消息、内存映射等,一个进程的所有线程都处于此进程的地址空间中,这样线程的通信与进程相比要简单很多。 1.使用全局变量 2.使用自定义消息 线程同步 虽然多线程能带来好处,但是也有不少问题需要解决。 例如,对于某一个数据来说,有可能一个线程访问这个数据,并且对其进行了更新,那么在另外上网线程中访问该数据时得是更新前的数值呢? 还是更新后的数值,在程序中有必要对其进行管理,这就是同步问题。 使隶属于同一进程的各线程协调一致地工作称为线程的同步。 MFC提供了多种同步对象,下面只介绍最常用的四种: 临界区(CCriticalSection)、事件(CEvent)、互斥量(CMutex)和信号量(CSemaphore)。 下面就这四种方式分别进行分别介绍。 1.使用CCriticalSection类 2.使用CEvent类 3.使用CMutex类 4.使用CSemaphore类 Microsoft基础类库(MicrosoftFoundationClassLibrary,MFC)的本质是一个包含了许多已经定义好了的类的类库。 MFC是微软提供的,封装了大量WindowsAPI的C++类库,它基本封装了Windows的所有API函数,因此利用MFC建立应用程序更加符合面向对象的思想。 同时利用向导所建立的应用程序隐藏了程序设计的很多细节,例如消息的管理和设备环境绘图。 窗口类和它的派生类都封装了Windows窗口句柄。 窗口类一般包括窗口支持类、窗口框架类、视图类、控件类和对话框类。 窗口支持类CWnd,CWnd是Windows所有窗口类的基类,它所包含的函数为基本的窗口操作
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- mfc 线程