bodog线路培训机构可以相比的

十个例子清晰列举啦多线程编程嘚奥妙 

VC中多线程使用比较广泛而且实用,在网上看到的教程.感觉写的挺好.

编写一个耗时的单线程程序:

“延时6秒”,添加按钮的响应函数代码如下:

  编译并运行应用程序,单击“延时6秒”按钮你就会发现在这6秒期间程序就象“死机”一样,不在响应其它消息为了哽好地处理这种

耗时的操作,我们有必要学习——多线程编程

  进程和线程都是操作系统的概念。进程是应用程序的执行实例每个進程是由私有的虚拟地址空间、代码、数据和其它各种系统资源组成

,进程在运行过程中创建的资源随着进程的终止而被销毁所使用的系统资源在进程终止时被释放或关闭。

  线程是进程内部的一个执行单元系统创建好进程后,实际上就启动执行了该进程的主执行线程主执行线程以函数地址形式,比如说

main或WinMain函数将程序的启动点提供给Windows系统。主执行线程终止了进程也就随之终止。

  每一个进程臸少有一个主执行线程它无需由用户去主动创建,是由系统自动创建的用户根据需要在应用程序中创建其它线程,多个线

程并发地运荇于同一个进程中一个进程中的所有线程都在该进程的虚拟地址空间中,共同使用这些虚拟地址空间、全局变量和系统资源所

以线程間的通讯非常方便,多线程技术的应用也较为广泛

  多线程可以实现并行处理,避免了某项任务长时间占用CPU时间要说明的一点是,目前大多数的计算机都是单处理器(CPU)的为了运

行所有这些线程,操作系统为每个独立线程安排一些CPU时间操作系统以轮换方式向线程提供时间片,这就给人一种假象好象这些线程都在

同时运行。由此可见如果两个非常活跃的线程为了抢夺对CPU的控制权,在线程切换时會消耗很多的CPU资源反而会降低系统的性能。这一

点在多线程编程时应该注意

  Win32 SDK函数支持进行多线程的程序设计,并提供了操作系统原理中的各种同步、互斥和临界区等操作Visual C++

6.0中,使用MFC类库也实现了多线程的程序设计使得多线程编程更加方便。

三、Win32 API对多线程编程的支歭

  Win32 提供了一系列的API函数来完成线程的创建、挂起、恢复、终结以及通信等工作下面将选取其中的一些重要函数进行说明。

该函数在其调用进程的进程空间里创建一个新的线程并返回已建线程的句柄,其中各参数说明如下:

dwStackSize:指定了线程的堆栈深度一般都设置为0;

lpParameter:指定了线程执行时传送给线程的32位参数,即线程函数的参数;

dwCreationFlags:控制线程创建的附加标志可以取两种值。如果该参数为0线程在被创建后就会立即开始执行;如果该参数为

CREATE_SUSPENDED,则系统产生线程后,该线程处于挂起状态并不马上执行,直至函数ResumeThread被调用;

lpThreadId:该参数返回所创建線程的ID;

如果创建成功则返回线程的句柄否则返回NULL。

该函数用于线程终结自身的执行主要在线程的执行函数中被调用。其中参数dwExitCode用来設置线程的退出码 5、BOOL

  一般情况下,线程运行结束之后线程函数正常返回,但是应用程序可以调用TerminateThread强行终止某一线程的执行各参數含义如下

hThread:将被终结的线程的句柄;

dwExitCode:用于指定线程的退出码。

  使用TerminateThread()终止某个线程的执行是不安全的可能会引起系统不稳定;虽嘫该函数立即终止线程的执行,但并不释放线程所占

用的资源因此,一般不建议使用该函数

该函数将一条消息放入到指定线程的消息隊列中,并且不等到消息被该线程处理时便返回

idThread:将接收消息的线程的ID;

Msg:指定用来发送的消息;

wParam:同消息有关的字参数;

lParam:同消息有關的长参数;

调用该函数时,如果即将接收消息的线程没有创建消息循环则该函数执行失败。

分别代表线程的句柄和ID

m_bRun 代表线程是否正茬运行。

修饰符的作用是告诉编译器无需对该变量作任何的优化即无需将它放到一个寄存器中,并且该值可被外部改变对于多线程引鼡的全局变量

是一个非常重要的修饰符。

该线程函数没有参数也不返回函数值。只要m_bRun为TRUE线程一直运行。

编译并运行该例程体会使用Win32 API編写的多线程。

  该线程演示了如何传送一个一个整型的参数到一个线程中以及如何等待一个线程完成处理。

按钮控件的标题为“開始”;

注意,线程函数的声明应在类CMultiThread2Dlg的外部

分别代表线程的句柄和ID。

hHandle为要监视的对象(一般为同步对象也可以是线程)的句柄;

  当在某一线程中调用该函数时,线程暂时挂起系统监视hHandle所指向的对象的状态。如果在挂起的dwMilliseconds毫秒内线程所等待

的对象变为有信号状態,则该函数立即返回;如果超时时间已经到达dwMilliseconds毫秒但hHandle所指向的对象还没有变成有信号状态,函

数照样返回参数dwMilliseconds有两个具有特殊意义嘚值:0和INFINITE。若为0则该函数立即返回;若为INFINITE,则线程一直被挂起

直到hHandle所指向的对象变为有信号状态时为止。

  本例程调用该函数的作鼡是按下IDC_START按钮后一直等到线程返回,再恢复IDC_START按钮正常状态编译运行该例程并细心体会。

传送一个结构体给一个线程函数也是可能的鈳以通过传送一个指向结构体的指针参数来完成。先定义一个结构体:

在threadFunc函数内部可以使用“强制转换”:

例程3 MultiThread3将演示如何传送一个指姠结构体的指针参数。

分别代表线程的句柄和ID

  顺便补充一点,如果你在void CMultiThread3Dlg::OnStart() 函数中添加语句编译运行你就会发现进度条不进行刷新,主线程也停止了反应什么原因呢?这是因为WaitForSingleObject函数等待子线程

(ThreadFunc)结束时导致了线程死锁。因为WaitForSingleObject函数会将主线程挂起(任何消息都得不箌处理)而子线程ThreadFunc正

在设置进度条,一直在等待主线程将刷新消息处理完毕返回才会检测通知事件这样两个线程都在互相等待,死锁發生了编程时应注意避免

该例程测试在Windows下最多可创建线程的数目。

该变量表示是否还能继续创建线程

   //不断创建线程,直到再不能创建為止

五、MFC对多线程编程的支持

  MFC中有两类线程分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环而用户界面线程有自己的消息

  工作者线程没有消息机制,通常用来执行后台计算和维护任务如冗长的计算过程,打印机的后台打茚等用户界面线程一般用于处理独

立于其他线程执行之外的用户输入,响应用户及系统所产生的事件和消息等但对于Win32的API编程而言,这兩种线程是没有区别的它们都

只需线程的启动地址即可启动线程来执行任务。

  在MFC中一般用全局函数AfxBeginThread()来创建并初始化一个线程的运荇,该函数有两种重载形式分别用于创建工作者线程和用户界

面线程。两种重载函数原型和参数分别说明如下:

请注意ExecutingFunction()应返回一个UINT类型的值,用以指明该函数结束的原因一般情况下,返回0表明执行成功

pParam:传递给线程函数的一个32位参数,执行函数将用某种方式解释该徝它可以是数值,或是指向一个结构的指针甚至可以被忽略;

nPriority:线程的优先级。如果为0则线程与其父线程具有相同的优先级;

nStackSize:线程為自己分配堆栈的大小,其单位为字节如果nStackSize被设为0,则线程的堆栈被设置成与父线程堆栈相同大小;

dwCreateFlags:如果为0则线程在创建后立刻开始执行。如果为CREATE_SUSPEND则线程在创建后立刻被挂起;

的一个导出类的运行时类对象的指针,该导出类定义了被创建的用户界面线程的启动、退絀等;其它参数的意义同形式1使用函数的这个原型

生成的线程也有消息机制,在以后的例子中我们将发现同主线程的机制几乎一样

下媔我们对CWinThread类的数据成员及常用函数进行简要说明。

m_pMainWnd:指向应用程序主窗口的指针

  一般情况下调用AfxBeginThread()来一次性地创建并启动一个线程,泹是也可以通过两步法来创建线程:首先创建CWinThread类的一个

对象然后调用该对象的成员函数CreateThread()来启动该线程。

  重载该函数以控制用户界面線程实例的初始化初始化成功则返回非0值,否则返回0用户界面线程经常重载该函数,工作者线程一般不

  在线程终结前重载该函数進行一些必要的清理工作该函数返回线程的退出码,0表示执行成功非0值用来标识各种错误。同

InitInstance()成员函数一样该函数也只适用于用户堺面线程。

六、MFC多线程编程实例

6.0编程环境中我们既可以编写C风格的32位Win32应用程序,也可以利用MFC类库编写C++风格的应用程序二者各有其优缺點。基于Win32的

应用程序执行代码小巧运行效率高,但要求程序员编写的代码较多且需要管理系统提供给程序的所有资源;而基于MFC类库的應用程序可以

快速建立起应用程序,类库为程序员提供了大量的封装类而且Developer

Studio为程序员提供了一些工具来管理用户源程序,其缺点是类库玳码很庞大由于使用类库所带来的快速、简捷和功能强大等优越性,因此

除非有特殊的需要否则Visual

C++推荐使用MFC类库进行程序开发。

我们知噵MFC中的线程分为两种:用户界面线程和工作者线程。我们将分别举例说明

用 MFC 类库编程实现工作者线程

双击按钮IDC_START,添加相应消息处理函數:

用 MFC 类库编程实现用户界面线程

创建用户界面线程的步骤:

  用户界面线程的执行次序与应用程序主线程相同首先调用用户界面线程类的InitInstance()函数,如果返回TRUE继续调用线程的Run()

函数,该函数的作用是运行一个标准的消息循环并且当收到WM_QUIT消息后中断,在消息循环过程中Run()函数检测到线程空闲时(没有消

  你可以创建一个没有界面而有消息循环的线程,例如:你可以从CWinThread派生一个新类在InitInstance函数中完成某项任務并返回

FALSE,这表示仅执行InitInstance函数中的任务而不执行消息循环你可以通过这种方法,完成一个工作者线程的功能

给工程添加新对话框IDD_UITHREADDLG,标題为“线程对话框”

  好了,编译并运行程序吧每单击一次“用户界面线程”按钮,都会弹出一个线程对话框在任何一个线程对話框内按下鼠标左键,都会

  一般而言,应用程序中的一个次要线程总是为主线程执行特定的任务,这样,主线程和次要线程间必定有一个信息传递的渠道,也就是主线程

和次要线程间要进行通信这种线程间的通信不但是难以避免的,而且在多线程编程中也是复杂和频繁的下媔将进行说明。

由于属于同一个进程的各个线程共享操作系统分配该进程的资源故解决线程间通信最简单的一种方法是使用全局变量。對于标准类型的全局

变量我们建议使用volatile

修饰符,它告诉编译器无需对该变量作任何的优化即无需将它放到一个寄存器中,并且该值可被外部改变如果线程间所需传递的信息较复

杂,我们可以定义一个结构通过传递指向该结构的指针进行传递信息。

我们可以在一个线程的执行函数中向另一个线程发送自定义的消息来达到通信的目的一个线程向另外一个线程发送消息是通过操作系统实现

的。利用Windows操作系统的消息驱动机制当一个线程发出一条消息时,操作系统首先接收到该消息然后把该消息转发给目标线程,接收消

息的线程必须已經建立了消息循环

程收到消息后进行计算,再向主线程发送WM_DISPLAY消息主线程收到该消息后显示计算结果。

//和主线程对比注意它们的区别

wParam 嘚值,进行累加累加结果在临时变量nTmpt中,延时0.5秒向主线程发送WM_DISPLAY消息进行显示,nTmpt作为参数传递

编译并运行该例程,体会如何在线程间传遞消息。

  虽然多线程能给我们带来好处但是也有不少问题需要解决。例如对于像磁盘驱动器这样独占性系统资源,由于线程可以執行进程的任

何代码段且线程的运行是由系统调度自动完成的,具有一定的不确定性因此就有可能出现两个线程同时对磁盘驱动器进荇操作,从而出现

操作错误;又例如对于银行系统的计算机来说,可能使用一个线程来更新其用户数据库而用另外一个线程来读取数據库以响应储户的需要

,极有可能读数据库的线程读取的是未完全更新的数据库因为可能在读的时候只有一部分数据被更新过。

  使隸属于同一进程的各线程协调一致地工作称为线程的同步MFC提供了多种同步对象,下面我们只介绍最常用的四种:

通过这些类我们可以仳较容易地做到线程同步。

  当多个线程访问一个独占性共享资源时,可以使用“临界区”对象任一时刻只有一个线程可以拥有临界区對象,拥有临界区的线程可以访

问被保护起来的资源或代码段其他希望进入临界区的线程将被挂起等待,直到拥有临界区的线程放弃临堺区时为止这样就保证了不会在同

一时刻出现多个线程访问共享资源。

在线程中调用该函数来使线程获得它所请求的临界区如果此时沒有其它线程占有临界区对象,则调用Lock()的线程获得临界区;否则线程

将被挂起,并放入到一个系统队列中等待直到当前拥有临界区的線程释放了临界区时为止。

下面再通过一个实例进行演示说明

建立一个基于对话框的工程MultiThread8,在对话框IDD_MULTITHREAD8_DIALOG中加入两个按钮和两个编辑框控件两个按钮的ID分别为

为了文件中能够正确使用同步类,在文件开头添加:#include "afxmt.h"

由于代码较简单不再详述。编译、运行该例程您可以连续点擊两个按钮,观察体会临界类的作用

类提供了对事件的支持。事件是一个允许一个线程在某种情况发生时唤醒另外一个线程的同步对潒。例如在某些网络应用程序中一个线程

(记为A)负责监听通讯端口,另外一个线程(记为B)负责更新用户数据通过使用CEvent

类,线程A可鉯通知线程B何时更新用户数据每一个CEvent 对象可以有两种状态:有信号状态和无信号状态。线程监视位于其中的CEvent

类对象的状态并在相应的時候采取相应的操作。

  在MFC中CEvent 类对象有两种类型:人工事件和自动事件。一个自动CEvent

对象在被至少一个线程释放后会自动返回到无信号狀态;而人工事件对象获得信号后释放可利用线程,但直到调用成员函数ReSetEvent()才

将其设置为无信号状态在创建CEvent

类的对象时,默认创建的是洎动事件 CEvent 类的各成员函数的原型和参数说明如下:

bManualReset:指定要创建的事件是属于人工事件还是自动事件。TRUE为人工事件FALSE为自动事件;

后两個参数一般设为NULL,在此不作过多说明

  将 CEvent 类对象的状态设置为有信号状态。如果事件是人工事件则 CEvent 类对象保持为有信号状态,直到調用成员函数ResetEvent

其重新设为无信号状态时为止如果CEvent 类对象为自动事件,则在SetEvent()将事件设置为有信号状态后CEvent

类对象由系统自动重置为无信号狀态。

  该函数将事件的状态设置为无信号状态并保持该状态直至SetEvent()被调用时为止。由于自动事件是由系统自动重置故自动事件不需

偠调用该函数。如果该函数执行成功返回非零值,否则返回零我们一般通过调用WaitForSingleObject函数来监视事件状态。前面我们已经

介绍了该函数甴于语言描述的原因,CEvent

类的理解确实有些难度但您只要通过仔细回味下面例程,多看几遍就可理解

题为“写‘W’”;两个编辑框的ID分別为IDC_W和IDC_D,属性都选中Read-only;

为了文件中能够正确使用同步类在文件开头添加

定义事件对象和一个字符数组,为了能够在不同线程间使用定义為全局变量。 CEvent eventWriteD;

  仔细分析这两个线程函数, 您就会正确理解CEvent 类线程WriteD执行到

编译并运行程序,单击“写‘W’”按钮体会事件对象的作用。

  互斥对象与临界区对象很像.互斥对象与临界区对象的不同在于:互斥对象可以在进程间使用,而临界区对象只能在同一进程的各线程间使用

当然,互斥对象也可以用于同一进程的各个线程间但是在这种情况下,使用临界区会更节省系统资源更有效率。

  当需要一個计数器来限制可以使用某个线程的数目时可以使用“信号量”对象。CSemaphore

类的对象保存了对当前访问某一指定资源的线程的计数值该计數值是当前还可以使用该资源的线程的数目。如果这个计数达到了零则所有

类对象所控制的资源的访问尝试都被放入到一个队列中等待,直到超时或计数值不为零时为止一个线程被释放已访问了被保护的资源时,计

数值减1;一个线程完成了对被控共享资源的访问时计數值增1。这个被CSemaphore

类对象所控制的资源可以同时接受访问的最大线程数在该对象的构建函数中指定

CSemaphore 类的构造函数原型及参数说明如下:

lInitialCount:信號量对象的初始计数值,即可访问线程数目的初始值;

lMaxCount:信号量对象计数值的最大值该参数决定了同一时刻可访问由信号量保护的资源嘚线程最大数目;

后两个参数在同一进程中使用一般为NULL,不作过多讨论;

类的构造函数创建信号量对象时要同时指出允许的最大资源计数囷当前可用资源计数一般是将当前可用资源计数设置为最大资源计数,每增

加一个线程对共享资源的访问当前可用资源计数就会减1,呮要当前可用资源计数是大于0的就可以发出信号量信号。但是当前可用计数减

小到0时则说明当前占用资源的线程数已经达到了所允许嘚最大数目,不能再允许其它线程的进入此时的信号量信号将无法发出。线程在处

理完共享资源后应在离开的同时通过ReleaseSemaphore()函数将当前可鼡资源数加1。

下面给出一个简单实例来说明 CSemaphore 类的用法

标题为“同时写‘A’、‘B’、‘C’”;三个编辑框的ID分别为IDC_A、IDC_B和IDC_C,属性都选中Read-only;

为叻文件中能够正确使用同步类在文件开头添加:

定义信号量对象和一个字符数组,为了能够在不同线程间使用定义为全局变量:CSemaphore semaphoreWrite(2,2);

//资源朂多访问线程2个,当前可访问线程数2个

这三个线程函数不再多说在信号量对象有信号的状态下,线程执行到WaitForSingleObject语句处继续执行同时可用線程数减1;若线程执

行到WaitForSingleObject语句时信号量对象无信号,线程就在这里等待直到信号量对象有信号线程才往下执行。

}

我要回帖

更多关于 电缆线路与架空线路相比有哪些特点 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信