VISUAL C++ 系统及硬件编程
硬件篇
编程实现测试CPU的速度
CPU的速度随温度和电压的变化而变化,如何随时查看CPU的速度?下面我们通过编程实现。在这个过程中,要用到汇编语言的知识。
第一步:生成一个基于对话框的工程CPUSpeed。其他选项我们可以都取其默认值。
第二步:在对话框上添加一个按钮,名称为"测试CPU速度",双击此按钮生成此按钮的处理函数,OnButton1。
第三步:在CPUSpeedDlg.cpp文件中定义类Ctime,在OnButton1中添加处理代码,最后文件CPUSpeedDlg.cpp变成如下:
好了,现在点击按钮"测试CPU速度"就可以弹出对话框告诉我们CPU的速度了。
程序中使用自定义的鼠标
?建立工程与一个资源档
用Image Editor编辑一个鼠游标
(Fild | New | Resource File)
新建一个 CURSOR_1 的 CURSOR, 设定好它的 Hot Spot
(Cursor | Set Hot Spot)
存档时注意要和建立的Project存在同一个目录在本例我们先假定为 MyCursor.res
二. 程序部分
定义一个常数crMyCursor, 这个常数您必须设成大於零的任何整数, 以 LoadCursor() 函数将自订的鼠标资源 load 进来, 以下为源代码:
// unit.pas
unit Unit1;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes,
Graphics, Controls, Forms, Dialogs;
const
crMyCursor = 1; (* 宣告一个常数 *)
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
{$R mycursor.res}//这行$R不可少, 否则自订的鼠游标就出不来
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
//将鼠标资源 load 进来
Screen.Cursors[crMyCursor] := LoadCursor
(hInstance,CURSOR_1);
Cursor := crMyCursor;//指定 form1 的 cursor 自订鼠标
Button1.Cursor := crMyCursor;//指定Button1的cursor为自订?
标
end;
end.
进程与线程
Windows多线程多任务设计初步
[前言:]当前流行的Windows操作系统,它能同时运行几个程序(独立运行的程序又称之为进程),对于同一个程序,它又可以分成若干个独立的执行流,我们称之为线程,线程提供了多任务处理的能力。用进程和线程的观点来研究软件是当今普遍采用的方法,进程和线程的概念的出现,对提高软件的并行性有着重要的意义。现在的应用软件无一不是多线程多任务处理,单线城的软件是不可想象的。因此掌握多线程多任务设计方法对每个程序员都是必需要掌握的。本文针对多线程技术在应用中经常遇到的问题,如线程间的通信、同步等,对它们分别进行探讨。
一、 理解线程
要讲解线程,不得不说一下进程,进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它系统资源组成。进程在运行时创建的资源随着进程的终止而死亡。线程的基本思想很简单,它是一个独立的执行流,是进程内部的一个独立的执行单元,相当于一个子程序,它对应Visual C++中的CwinThread类的对象。单独一个执行程序运行时,缺省的运行包含的一个主线程,主线程以函数地址的形式,如main或WinMain函数,提供程序的启动点,当主线程终止时,进程也随之终止,但根据需要,应用程序又可以分解成许多独立执行的线程,每个线程并行的运行在同一进程中。
一个进程中的所有线程都在该进程的虚拟地址空间中,使用该进程的全局变量和系统资源。操作系统给每个线程分配不同的CPU时间片,在某一个时刻,CPU只执行一个时间片内的线程,多个时间片中的相应线程在CPU内轮流执行,由于每个时间片时间很短,所以对用户来说,仿佛各个线程在计算机中是并行处理的。操作系统是根据线程的优先级来安排CPU的时间,优先级高的线程优先运行,优先级低的线程则继续等待。
线程被分为两种:用户界面线程和工作线程(又称为后台线程)。用户界面线程通常用来处理用户的输入并响应各种事件和消息,其实,应用程序的主执行线程CWinAPP对象就是一个用户界面线程,当应用程序启动时自动创建和启动,同样它的终止也意味着该程序的结束,进城终止。工作者线程用来执行程序的后台处理任务,比如计算、调度、对串口的读写操作等,它和用户界面线程的区别是它不用从CwinThread类派生来创建,对它来说最重要的是如何实现工作线程任务的运行控制函数。工作线程和用户界面线程启动时要调用同一个函数的不同版本;最后需要读者明白的是,一个进程中的所有线程共享它们父进程的变量,但同时每个线程可以拥有自己的变量。
二、 线程的管理和操作
1. 线程的启动
创建一个用户界面线程,首先要从类CwinThread产生一个派生类,同时必须使用DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE来声明和实现这个CwinThread派生类。
第二步是根据需要重载该派生类的一些成员函数如:ExitInstance();InitInstance();OnIdle();PreTranslateMessage()等函数,最后启动该用户界面线程,调用AfxBeginThread()函数的一个版本:CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );其中第一个参数为指向定义的用户界面线程类指针变量,第二个参数为线程的优先级,第三个参数为线程所对应的堆栈大小,第四个参数为线程创建时的附加标志,缺省为正常状态,如为CREATE_SUSPENDED则线程启动后为挂起状态。
对于工作线程来说,启动一个线程,首先需要编写一个希望与应用程序的其余部分并行运行的函数如Fun1(),接着定义一个指向CwinThread对象的指针变量*pThread,调用AfxBeginThread(Fun1,param,priority)函数,返回值付给pThread变量的同时一并启动该线程来执行上面的Fun1()函数,其中Fun1是线程要运行的函数的名字,也既是上面所说的控制函数的名字,param是准备传送给线程函数Fun1的任意32位值,priority则是定义该线程的优先级别,它是预定义的常数,读者可参考MSDN。
2.线程的优先级
以下的CwinThread类的成员函数用于线程优先级的操作:
上述的二个函数分别用来获取和设置线程的优先级,这里的优先级,是相对于该线程所处的优先权层次而言的,处于同一优先权层次的线程,优先级高的线程先运行;处于不同优先权层次上的线程,谁的优先权层次高,谁先运行。至于优先级设置所需的常数,自己参考MSDN就可以了,要注意的是要想设置线程的优先级,这个线程在创建时必须具有THREAD_SET_INFORMATION访问权限。对于线程的优先权层次的设置,CwinThread类没有提供相应的函数,但是可以通过Win32 SDK函数GetPriorityClass()和SetPriorityClass()来实现。
3.线程的悬挂、恢复
CwinThread类中包含了应用程序悬挂和恢复它所创建的线程的函数,其中SuspendThread()用来悬挂线程,暂停线程的执行;ResumeThread()用来恢复线程的执行。如果你对一个线程连续若干次执行SuspendThread(),则需要连续执行相应次的ResumeThread()来恢复线程的运行。
4.结束线程
终止线程有三种途径,线程可以在自身内部调用AfxEndThread()来终止自身的运行;可以在线程的外部调用BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode )来强行终止一个线程的运行,然后调用CloseHandle()函数释放线程所占用的堆栈;第三种方法是改变全局变量,使线程的执行函数返回,则该线程终止。下面以第三种方法为例,给出部分代码:
三、 线程之间的通信
通常情况下,一个次级线程要为主线程完成某种特定类型的任务,这就隐含着表示在主线程和次级线程之间需要建立一个通信的通道。一般情况下,有下面的几种方法实现这种通信任务:使用全局变量(上一节的例子其实使用的就是这种方法)、使用事件对象、使用消息。这里我们主要介绍后两种方法。
1. 利用用户定义的消息通信
在Windows程序设计中,应用程序的每一个线程都拥有自己的消息队列,甚至工作线程也不例外,这样一来,就使得线程之间利用消息来传递信息就变的非常简单。首先用户要定义一个用户消息,如下所示:#define WM_USERMSG WMUSER+100;在需要的时候,在一个线程中调用
::PostMessage((HWND)param,WM_USERMSG,0,0)
或
CwinThread::PostThradMessage()
来向另外一个线程发送这个消息,上述函数的四个参数分别是消息将要发送到的目的窗口的句柄、要发送的消息标志符、消息的参数WPARAM和LPARAM。下面的代码是对上节代码的修改,修改后的结果是在线程结束时显示一个对话框,提示线程结束:
上面的例子是工作者线程向用户界面线程发送消息,对于工作者线程,如果它的设计模式也是消息驱动的,那么调用者可以向它发送初始化、退出、执行某种特定的处理等消息,让它在后台完成。在控制函数中可以直接使用::GetMessage()这个SDK函数进行消息分检和处理,自己实现一个消息循环。GetMessage()函数在判断该线程的消息队列为空时,线程将系统分配给它的时间片让给其它线程,不无效的占用CPU的时间,如果消息队列不为空,就获取这个消息,判断这个消息的内容并进行相应的处理。
2.用事件对象实现通信
在线程之间传递信号进行通信比较复杂的方法是使用事件对象,用MFC的Cevent类的对象来表示。事件对象处于两种状态之一:有信号和无信号,线程可以监视处于有信号状态的事件,以便在适当的时候执行对事件的操作。上述例子代码修改如下:
运行这个程序,当关闭程序时,才显示提示框,显示"Thread ended"
四、 线程之间的同步
前面我们讲过,各个线程可以访问进程中的公共变量,所以使用多线程的过程中需要注意的问题是如何防止两个或两个以上的线程同时访问同一个数据,以免破坏数据的完整性。保证各个线程可以在一起适当的协调工作称为线程之间的同步。前面一节介绍的事件对象实际上就是一种同步形式。Visual C++中使用同步类来解决操作系统的并行性而引起的数据不安全的问题,MFC支持的七个多线程的同步类可以分成两大类:同步对象(CsyncObject、Csemaphore、Cmutex、CcriticalSection和Cevent)和同步访问对象(CmultiLock和CsingleLock)。本节主要介绍临界区(critical section)、互斥(mutexe)、信号量(semaphore),这些同步对象使各个线程协调工作,程序运行起来更安全。
1. 临界区
临界区是保证在某一个时间只有一个线程可以访问数据的方法。使用它的过程中,需要给各个线程提供一个共享的临界区对象,无论哪个线程占有临界区对象,都可以访问受到保护的数据,这时候其它的线程需要等待,直到该线程释放临界区对象为止,临界区被释放后,另外的线程可以强占这个临界区,以便访问共享的数据。临界区对应着一个CcriticalSection对象,当线程需要访问保护数据时,调用临界区对象的Lock()成员函数;当对保护数据的操作完成之后,调用临界区对象的Unlock()成员函数释放对临界区对象的拥有权,以使另一个线程可以夺取临界区对象并访问受保护的数据。同时启动两个线程,它们对应的函数分别为WriteThread()和ReadThread(),用以对公共数组组array[]操作,下面的代码说明了如何使用临界区对象:
上述代码运行的结果应该是Destarray数组中的元素分别为1-9,而不是杂乱无章的数,如果不使用同步,则不是这个结果,有兴趣的读者可以实验一下
2. 互斥
互斥与临界区很相似,但是使用时相对复杂一些,它不仅可以在同一应用程序的线程间实现同步,还可以在不同的进程间实现同步,从而实现资源的安全共享。互斥与Cmutex类的对象相对应,使用互斥对象时,必须创建桓鯟SingleLock或CMultiLock对象,用于实际的访问控制,因为这里的例子只处理单个互斥,所以我们可以使用CSingleLock对象,该对象的Lock()函数用于占有互斥,Unlock()用于释放互斥。实现代码如下:
3. 信号量
信号量的用法和互斥的用法很相似,不同的是它可以同一时刻允许多个线程访问同一个资源,创建一个信号量需要用Csemaphore类声明一个对象,一旦创建了一个信号量对象,就可以用它来对资源的访问技术。要实现计数处理,先创建一个CsingleLock或CmltiLock对象,然后用该对象的Lock()函数减少这个信号量的计数值,Unlock()反之。下面的代码分别启动三个线程,执行时同时显示二个消息框,然后10秒后第三个消息框才得以显示。
对复杂的应用程序来说,线程的应用给应用程序提供了高效、快速、安全的数据处理能力。本文讲述了线程中经常遇到的问题,希望对读者朋友有一定的帮助。
例程分析多线程编程
Windows系统平台经历了16位到32位的转变后,系统运行方式和任务管理方式有了很大的变化,在Windows 95和Windows NT中,每个Win32程序在独立的进程空间上运行,32位地址空间使我们从16位段式结构的64K段限制中摆脱出来,逻辑上达到了4G的线性地址空间,我们在设计程序时,不再需要考虑编译的段模式,同时还提高了大程序的运行效率。独立进程空间的另一个更大的优越性是大大提高了系统的稳定性,一个应用的异常错误不会影响其它的应用。与在MS-DOS和16位Windows操作系统中不同,32位Windows进程是没有活力的。这就是说,一个32位Windows进程并不执行什么指令,它只是占据着4GB的地址空间,此空间中有应用程序EXE文件的代码和数据。EXE需要的DLL也将它们的代码的数据装入到进程的地址空间。除了地址空间,进程还占有某些资源,比如文件、动态内存分配和线程。当进程终止时,在它生命期中创建的各种资源将被清除。
如上所述,进程是没有活力的,它只是一个静态的概念。为了让进程完成一些工作,进程必须至少占有一线程,所以线程是描述进程内的执行,正是线程负责执行包含在进程的地址空间中的代码。实际上,单个进程可能包含几个线程,它们可以同时执行进程的地址空间中的代码。为了做到这一点,每个线程有自己的一组CPU寄存器和椎。每个进程至少有一个线址程在执行其地址空间中的代码,如果没有线程执行进程地空间中的代码,如果没有线程执行进程地址空间中的代码,进程也就没有继续存在的理由,系统将自动清除进程及其地址空间。为了运行所有这些线程,操作系统为每个独立线程安排一些CPU时间,操作系统以轮转方式向线程提供时间片,这就给人一种假象,好象这些线程都在同时运行。创建一个32位Windows进程时,它的第一个线程称为主线程,由系统自动生成,然后可由这个主线程生成额外的线程,这些线程又可生成更多的线程。
例如,在基于Internet网上的可视电话系统中,同时要进行语音采集、语音编译码、图像采集、图像编译码、语音和图像码流的传输,所有这些工作,都要并行处理。特别是语音信号,如果进行图像编解码时间过长,语音信号得不到服务,通话就有间断;如果图像或语音处理时间过长,而不能及时传输码流数据,通信同样也会中断。这样就要求我们实现一种并行编程,在只有一个CPU的机器上,也就是要将该CPU时间按时一定的优先准则分配给各个事件,定期处理各事件,而不会对某一事件处理过长。
在32位Windows95或Windows NT下,我们可以用多线程的处理技术来实现这种并行处理。实际上,这种并行编程在很多场合下都是必须的。再例如,在File Manager拷贝文件时,它显示一个对话框中包含了一个Cancel按钮。如果在文件拷贝过程中,点中Cance l按钮,就会终止拷贝。在16位Winows中,实现这类功能需要在File Copy循环内部周期性地调用PeekMessage函数。如果正在读一个很大的动作;如果从软盘读文件,则要花费好几秒的时间。由于机器反应太迟钝,用户会频繁地点中这个按钮,以为系统不知道想终止这个操作。如果把File Copy指令放入另外一个线程,就不需要在代码中放一大堆PeekMessage函数,处理用户界面的线程将与它分开操作,点中Cancel按钮后会立即得到响应。同样的道理,在应用程序中创建一个单独线程来处理所有打印任务也是很有用的,用户可以在打印处理时继续使用应用程序。
多线程的编程在Win32方式下和MFC类库支持下的原理是一致的,进程的主线程在任何需要的时候都可以创建新的线程,当线程执行完任务后,自动终止线程,当进程结束后,所有的线程都终止。所有活动的线程共享进程的资源,所以在编程时,需要考虑在多个线程访问同一资源时,产生冲突的问题,当一个线程正在访问一个进程对象,这时另一个线程要改变该对象,这时可能会产生错误的结果,所以程序员在编程时要解决这种冲突。
下面举例说明在Win32 基础上进行多线程编程的过程。
1.使用函数说明
Win32函数库里提供了多线程控制的操作函数,包括创建线程、终止线程、建立互斥区等。首先,在应用程序的主线程或者其它活动线程的适当的地方创建新的线程,创建线程的函数如下:
HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId );
参数lpThreadAttributes 指定了线程的安全属性,在Windows 95中被忽略;
参数dwStackSize 指定了线程的堆栈深度;
参数lpStartAddress 指定了线程的起始地址,一般情况为下面原型的函数
DWORD WINAPI ThreadFunc( LPVOID );
参数 lpParameter指定了线程执行时传送给线程的32位参数,即上面函数的参数;
参数dwCreationFlags指定了线程创建的特性;
参数 lpThreadId 指向一个DWORD变量,可返回线程ID值。
如果创建成功则返回线程的句柄,否则返回NULL。
创建了新的线程后,线程开始启动执行,如果在dwCreationFlags中用了CREATE_SUSPENDED特性,那么线程并不马上执行,而是先挂起,等到调用ResumeThread后才开始启动线程,在这过程中可以调用函数
BOOL SetThreadPriority( HANDLE hThread, int nPriority);
设置线程的优先权。
当线程的函数返回后,线程自动终止,如果要想在线程的执行过程中终止的话,可以调用函数
VOID ExitThread( DWORD dwExitCode);
如果在线程的外面终止线程的话,可以用下面的函数
BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode );
但注意,该函数可能会引起系统不稳定,而且,线程所占用的资源也不释放,因此,一般情况下,建议不要使用该函数。
如果要终止的线程是进程内的最后一个线程的话,在线程被终止后,相应的进程也终止。
2. 无优先级例程,该例程实现在对话框上通过一线程实现时钟的显示和停止。步骤如下:
第一步:建立一基于对话框的工程MultiProcess1。
第二步:在对话框上建立两个按钮和一个编辑框,ID号分别为ID_START、ID_STOP和IDC_TIME,Caption分别为"启动"、"停止"。如下:
第三步:在MultiProcess1Dlg.cpp中定义全局函数ThreadProc(),格式如下:
第四步:在头文件MultiProcess1Dlg.h中定义变量如下:
DWORD ThreadID;
HANDLE hThread;
第五步:双击"开始"按钮,生成消息映射函数OnStart(),编写其中的代码如下:
此时即刻实现在对话框上点击"启动",启动时钟。接下来我们实现如何让时钟停下来。
第六步:双击"停止"按钮,添加停止的消息映射函数OnStop(),编写代码如下:
注意:该函数可能会引起系统不稳定,而且,线程所占用的资源也不释放,因此,一般情况下,建议不要使用该函数。
到现在,这个程序就完整了,看一下效果吧!
最后需要说明的是,并不是设计多线程就是一个好的程序。目前大多数的计算机都是单处理器(CPU)的,在这种机器上运行多线程程序,有时反而会降低系统性能,如果两个非常活跃的线程为了抢夺对CPU的控制权,在线程切换时会消耗很多的CPU资源,但对于大部分时间被阻塞的线程(例如等待文件 I/O 操作),可以用一个单独的线程来完成,这样可把CPU时间让出来,使程序获得更好的性能。因此,在设计多线程应用时,需要慎重选择,具体情况具体处理,以使应用程序获得最佳的性能。
用VC++5.0实现多线程的调度和处理
一多任务,多进程和多线程
---- Windows95 和WindowsNT 操作系统支持多任务调度和处理,基于该功能所提供的多任务空间,程序员可以完全控制应用程序中每一个片段的运行,从而编写高效率的应用程序。
---- 所谓多任务通常包括这样两大类:多进程和多线程。进程是指在系统中正在运行的一个应用程序;线程是系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元。对于操作系统而言,其调度单元是线程。一个进程至少包括一个线程,通常将该线程称为主线程。一个进程从主线程的执行开始进而创建一个或多个附加线程,就是所谓基于多线程的多任务。
---- 开发多线程应用程序可以利用32 位Windows 环境提供的Win32 API 接口函数,也可以利用VC++ 中提供的MFC 类库进行开发。多线程编程在这两种方式下原理是一样的,用户可以根据需要选择相应的工具。本文重点讲述用VC++5.0 提供的MFC 类库实现多线程调度与处理的方法以及由线程多任务所引发的同步多任务特征,最后详细解释一个实现多线程的例程。
二基于MFC 的多线程编程
---- 1 MFC 对多线程的支持
---- MFC 类库提供了多线程编程支持,对于用户编程实现来说更加方便。非常重要的一点就是,在多窗口线程情况下,MFC 直接提供了用户接口线程的设计。
---- MFC 区分两种类型的线程:辅助线程(Worker Thread)和用户界面线程(UserInterface Thread)。辅助线程没有消息机制,通常用来执行后台计算和维护任务。MFC 为用户界面线程提供消息机制,用来处理用户的输入,响应用户产生的事件和消息。但对于Win32 的API 来说,这两种线程并没有区别,它只需要线程的启动地址以便启动线程执行任务。用户界面线程的一个典型应用就是类CWinApp,大家对类CwinApp 都比较熟悉,它是CWinThread 类的派生类,应用程序的主线程是由它提供,并由它负责处理用户产生的事件和消息。类CwinThread 是用户接口线程的基本类。CWinThread 的对象用以维护特定线程的局部数据。因为处理线程局部数据依赖于类CWinThread,所以所有使用MFC 的线程都必须由MFC 来创建。例如,由run-time 函数_beginthreadex 创建的线程就不能使用任何MFC API。
---- 2 辅助线程和用户界面线程的创建和终止
---- 要创建一个线程,需要调用函数AfxBeginThread。该函数通过参数重载具有两种版本,分别对应辅助线程和用户? 线程。无论是辅助线程还是用户界面线程,都需要指定额外的参数以修改优先级,堆栈大小,创建标志和安全特性等。函数AfxBeginThread 返回指向CWinThread 类对象的指针。
---- 创建助手线程相对简单。只需要两步:实现控制函数和启动线程。它并不必须从CWinThread 派生一个类。简要说明如下:
---- 1. 实现控制函数。控制函数定义该线程。当进入该函数,线程启动;退出时,线程终止。该控制函数声明如下:
UINT MyControllingFunction( LPVOID pParam );
---- 该参数是一个单精度32 位值。该参数接收的值将在线程对象创建时传递给构造函数。控制函数将用某种方式解释该值。可以是数量值,或是指向包括多个参数的结构的指针,甚至可以被忽略。如果该参数是指结构,则不仅可以将数据从调用函数传给线程,也可以从线程回传给调用函数。如果使用这样的结构回传数据,当结果准备好的时候,线程要通知调用函数。当函数结束时,应返回一个UINT 类型的值值,指明结束的原因。通常,返回0 表明成功,其它值分别代表不同的错误。
---- 2. 启动线程。由函数AfxBeginThread 创建并初始化一个CWinThread 类的对象,启动并返回该线程的地址。则线程进入运行状态。
---- 3. 举例说明。下面用简单的代码说明怎样定义一个控制函数以及如何在程序的其它部分使用。
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);
……
创
编程实现测试CPU的速度
CPU的速度随温度和电压的变化而变化,如何随时查看CPU的速度?下面我们通过编程实现。在这个过程中,要用到汇编语言的知识。
第一步:生成一个基于对话框的工程CPUSpeed。其他选项我们可以都取其默认值。
第二步:在对话框上添加一个按钮,名称为"测试CPU速度",双击此按钮生成此按钮的处理函数,OnButton1。
第三步:在CPUSpeedDlg.cpp文件中定义类Ctime,在OnButton1中添加处理代码,最后文件CPUSpeedDlg.cpp变成如下:
// CPUSpeedDlg.cpp : implementation file // #include "stdafx.h" #include "CPUSpeed.h" #include "CPUSpeedDlg.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /////////////////////////////////////////////// // CAboutDlg dialog used for App About inline unsigned __int64 theCycleCount(void) { _asm _emit 0x0F _asm _emit 0x31 /// this causes a compiler warning as there is no return statement /// however the _emits return a __int64 value } class CTimer { unsigned __int64 m_start; public: unsigned __int64 m_overhead; CTimer(void) { m_overhead = 0; Start(); /// we get the start cycle m_overhead = Stop(); // then we get the stop cycle catching the overhead time } void Start(void) { m_start = theCycleCount(); } unsigned __int64 Stop(void) { /// with the stop cycle we remove the overhead"s time return theCycleCount()-m_start-m_overhead; } }; void CCPUSpeedDlg::OnButton1() { // TODO: Add your control notification handler code here CString strRawClockFrequency; CTimer timer; long tickslong; long tickslongextra; timer.Start(); Sleep(1000); unsigned cpuspeed100 = (unsigned)(timer.Stop()/10000); tickslong = cpuspeed100/100; tickslongextra = cpuspeed100-(tickslong*100); strRawClockFrequency.Format("%d.%d MHZ estimate ", tickslong,tickslongextra ); AfxMessageBox("CPU速度为"+strRawClockFrequency); } class CAboutDlg : public CDialog { ……以下为编程环境生成时自动生成的代码。 |
好了,现在点击按钮"测试CPU速度"就可以弹出对话框告诉我们CPU的速度了。
程序中使用自定义的鼠标
?建立工程与一个资源档
用Image Editor编辑一个鼠游标
(Fild | New | Resource File)
新建一个 CURSOR_1 的 CURSOR, 设定好它的 Hot Spot
(Cursor | Set Hot Spot)
存档时注意要和建立的Project存在同一个目录在本例我们先假定为 MyCursor.res
二. 程序部分
定义一个常数crMyCursor, 这个常数您必须设成大於零的任何整数, 以 LoadCursor() 函数将自订的鼠标资源 load 进来, 以下为源代码:
// unit.pas
unit Unit1;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes,
Graphics, Controls, Forms, Dialogs;
const
crMyCursor = 1; (* 宣告一个常数 *)
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
{$R mycursor.res}//这行$R不可少, 否则自订的鼠游标就出不来
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
//将鼠标资源 load 进来
Screen.Cursors[crMyCursor] := LoadCursor
(hInstance,CURSOR_1);
Cursor := crMyCursor;//指定 form1 的 cursor 自订鼠标
Button1.Cursor := crMyCursor;//指定Button1的cursor为自订?
标
end;
end.
进程与线程
Windows多线程多任务设计初步
[前言:]当前流行的Windows操作系统,它能同时运行几个程序(独立运行的程序又称之为进程),对于同一个程序,它又可以分成若干个独立的执行流,我们称之为线程,线程提供了多任务处理的能力。用进程和线程的观点来研究软件是当今普遍采用的方法,进程和线程的概念的出现,对提高软件的并行性有着重要的意义。现在的应用软件无一不是多线程多任务处理,单线城的软件是不可想象的。因此掌握多线程多任务设计方法对每个程序员都是必需要掌握的。本文针对多线程技术在应用中经常遇到的问题,如线程间的通信、同步等,对它们分别进行探讨。
一、 理解线程
要讲解线程,不得不说一下进程,进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它系统资源组成。进程在运行时创建的资源随着进程的终止而死亡。线程的基本思想很简单,它是一个独立的执行流,是进程内部的一个独立的执行单元,相当于一个子程序,它对应Visual C++中的CwinThread类的对象。单独一个执行程序运行时,缺省的运行包含的一个主线程,主线程以函数地址的形式,如main或WinMain函数,提供程序的启动点,当主线程终止时,进程也随之终止,但根据需要,应用程序又可以分解成许多独立执行的线程,每个线程并行的运行在同一进程中。
一个进程中的所有线程都在该进程的虚拟地址空间中,使用该进程的全局变量和系统资源。操作系统给每个线程分配不同的CPU时间片,在某一个时刻,CPU只执行一个时间片内的线程,多个时间片中的相应线程在CPU内轮流执行,由于每个时间片时间很短,所以对用户来说,仿佛各个线程在计算机中是并行处理的。操作系统是根据线程的优先级来安排CPU的时间,优先级高的线程优先运行,优先级低的线程则继续等待。
线程被分为两种:用户界面线程和工作线程(又称为后台线程)。用户界面线程通常用来处理用户的输入并响应各种事件和消息,其实,应用程序的主执行线程CWinAPP对象就是一个用户界面线程,当应用程序启动时自动创建和启动,同样它的终止也意味着该程序的结束,进城终止。工作者线程用来执行程序的后台处理任务,比如计算、调度、对串口的读写操作等,它和用户界面线程的区别是它不用从CwinThread类派生来创建,对它来说最重要的是如何实现工作线程任务的运行控制函数。工作线程和用户界面线程启动时要调用同一个函数的不同版本;最后需要读者明白的是,一个进程中的所有线程共享它们父进程的变量,但同时每个线程可以拥有自己的变量。
二、 线程的管理和操作
1. 线程的启动
创建一个用户界面线程,首先要从类CwinThread产生一个派生类,同时必须使用DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE来声明和实现这个CwinThread派生类。
第二步是根据需要重载该派生类的一些成员函数如:ExitInstance();InitInstance();OnIdle();PreTranslateMessage()等函数,最后启动该用户界面线程,调用AfxBeginThread()函数的一个版本:CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );其中第一个参数为指向定义的用户界面线程类指针变量,第二个参数为线程的优先级,第三个参数为线程所对应的堆栈大小,第四个参数为线程创建时的附加标志,缺省为正常状态,如为CREATE_SUSPENDED则线程启动后为挂起状态。
对于工作线程来说,启动一个线程,首先需要编写一个希望与应用程序的其余部分并行运行的函数如Fun1(),接着定义一个指向CwinThread对象的指针变量*pThread,调用AfxBeginThread(Fun1,param,priority)函数,返回值付给pThread变量的同时一并启动该线程来执行上面的Fun1()函数,其中Fun1是线程要运行的函数的名字,也既是上面所说的控制函数的名字,param是准备传送给线程函数Fun1的任意32位值,priority则是定义该线程的优先级别,它是预定义的常数,读者可参考MSDN。
2.线程的优先级
以下的CwinThread类的成员函数用于线程优先级的操作:
int GetThreadPriority(); BOOL SetThradPriority()(int nPriority); |
上述的二个函数分别用来获取和设置线程的优先级,这里的优先级,是相对于该线程所处的优先权层次而言的,处于同一优先权层次的线程,优先级高的线程先运行;处于不同优先权层次上的线程,谁的优先权层次高,谁先运行。至于优先级设置所需的常数,自己参考MSDN就可以了,要注意的是要想设置线程的优先级,这个线程在创建时必须具有THREAD_SET_INFORMATION访问权限。对于线程的优先权层次的设置,CwinThread类没有提供相应的函数,但是可以通过Win32 SDK函数GetPriorityClass()和SetPriorityClass()来实现。
3.线程的悬挂、恢复
CwinThread类中包含了应用程序悬挂和恢复它所创建的线程的函数,其中SuspendThread()用来悬挂线程,暂停线程的执行;ResumeThread()用来恢复线程的执行。如果你对一个线程连续若干次执行SuspendThread(),则需要连续执行相应次的ResumeThread()来恢复线程的运行。
4.结束线程
终止线程有三种途径,线程可以在自身内部调用AfxEndThread()来终止自身的运行;可以在线程的外部调用BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode )来强行终止一个线程的运行,然后调用CloseHandle()函数释放线程所占用的堆栈;第三种方法是改变全局变量,使线程的执行函数返回,则该线程终止。下面以第三种方法为例,给出部分代码:
//////////////////////////////////////////////////////////////// //////CtestView message handlers /////Set to True to end thread Bool bend=FALSE;//定义的全局变量,用于控制线程的运行 //The Thread Function UINT ThreadFunction(LPVOID pParam)//线程函数 { while(!bend) {Beep(100,100); Sleep(1000); } return 0; } CwinThread *pThread; HWND hWnd; ///////////////////////////////////////////////////////////// Void CtestView::OninitialUpdate() { hWnd=GetSafeHwnd(); pThread=AfxBeginThread(ThradFunction,hWnd);//启动线程 pThread->m_bAutoDelete=FALSE;//线程为手动删除 Cview::OnInitialUpdate(); } //////////////////////////////////////////////////////////////// Void CtestView::OnDestroy() { bend=TRUE;//改变变量,线程结束 WaitForSingleObject(pThread->m_hThread,INFINITE);//等待线程结束 delete pThread;//删除线程 Cview::OnDestroy(); } |
三、 线程之间的通信
通常情况下,一个次级线程要为主线程完成某种特定类型的任务,这就隐含着表示在主线程和次级线程之间需要建立一个通信的通道。一般情况下,有下面的几种方法实现这种通信任务:使用全局变量(上一节的例子其实使用的就是这种方法)、使用事件对象、使用消息。这里我们主要介绍后两种方法。
1. 利用用户定义的消息通信
在Windows程序设计中,应用程序的每一个线程都拥有自己的消息队列,甚至工作线程也不例外,这样一来,就使得线程之间利用消息来传递信息就变的非常简单。首先用户要定义一个用户消息,如下所示:#define WM_USERMSG WMUSER+100;在需要的时候,在一个线程中调用
::PostMessage((HWND)param,WM_USERMSG,0,0)
或
CwinThread::PostThradMessage()
来向另外一个线程发送这个消息,上述函数的四个参数分别是消息将要发送到的目的窗口的句柄、要发送的消息标志符、消息的参数WPARAM和LPARAM。下面的代码是对上节代码的修改,修改后的结果是在线程结束时显示一个对话框,提示线程结束:
UINT ThreadFunction(LPVOID pParam) { while(!bend) { Beep(100,100); Sleep(1000); } ::PostMessage(hWnd,WM_USERMSG,0,0); return 0; } ////////WM_USERMSG消息的响应函数为OnThreadended(WPARAM wParam,LPARAM lParam) LONG CTestView::OnThreadended(WPARAM wParam,LPARAM lParam) { AfxMessageBox("Thread ended."); Retrun 0; } |
上面的例子是工作者线程向用户界面线程发送消息,对于工作者线程,如果它的设计模式也是消息驱动的,那么调用者可以向它发送初始化、退出、执行某种特定的处理等消息,让它在后台完成。在控制函数中可以直接使用::GetMessage()这个SDK函数进行消息分检和处理,自己实现一个消息循环。GetMessage()函数在判断该线程的消息队列为空时,线程将系统分配给它的时间片让给其它线程,不无效的占用CPU的时间,如果消息队列不为空,就获取这个消息,判断这个消息的内容并进行相应的处理。
2.用事件对象实现通信
在线程之间传递信号进行通信比较复杂的方法是使用事件对象,用MFC的Cevent类的对象来表示。事件对象处于两种状态之一:有信号和无信号,线程可以监视处于有信号状态的事件,以便在适当的时候执行对事件的操作。上述例子代码修改如下:
//////////////////////////////////////////////////////////////////// Cevent threadStart,threadEnd; //////////////////////////////////////////////////////////////////// UINT ThreadFunction(LPVOID pParam) { ::WaitForSingleObject(threadStart.m_hObject,INFINITE); AfxMessageBox("Thread start."); while(!bend) { Beep(100,100); Sleep(1000); Int result=::WaitforSingleObject(threadEnd.m_hObject,0); //等待threadEnd事件有信号,无信号时线程在这里悬停 If(result==Wait_OBJECT_0) Bend=TRUE; } ::PostMessage(hWnd,WM_USERMSG,0,0); return 0; } ///////////////////////////////////////////////////////////// Void CtestView::OninitialUpdate() { hWnd=GetSafeHwnd(); threadStart.SetEvent();//threadStart事件有信号 pThread=AfxBeginThread(ThreadFunction,hWnd);//启动线程 pThread->m_bAutoDelete=FALSE; Cview::OnInitialUpdate(); } //////////////////////////////////////////////////////////////// Void CtestView::OnDestroy() { threadEnd.SetEvent(); WaitForSingleObject(pThread->m_hThread,INFINITE); delete pThread; Cview::OnDestroy(); } |
运行这个程序,当关闭程序时,才显示提示框,显示"Thread ended"
四、 线程之间的同步
前面我们讲过,各个线程可以访问进程中的公共变量,所以使用多线程的过程中需要注意的问题是如何防止两个或两个以上的线程同时访问同一个数据,以免破坏数据的完整性。保证各个线程可以在一起适当的协调工作称为线程之间的同步。前面一节介绍的事件对象实际上就是一种同步形式。Visual C++中使用同步类来解决操作系统的并行性而引起的数据不安全的问题,MFC支持的七个多线程的同步类可以分成两大类:同步对象(CsyncObject、Csemaphore、Cmutex、CcriticalSection和Cevent)和同步访问对象(CmultiLock和CsingleLock)。本节主要介绍临界区(critical section)、互斥(mutexe)、信号量(semaphore),这些同步对象使各个线程协调工作,程序运行起来更安全。
1. 临界区
临界区是保证在某一个时间只有一个线程可以访问数据的方法。使用它的过程中,需要给各个线程提供一个共享的临界区对象,无论哪个线程占有临界区对象,都可以访问受到保护的数据,这时候其它的线程需要等待,直到该线程释放临界区对象为止,临界区被释放后,另外的线程可以强占这个临界区,以便访问共享的数据。临界区对应着一个CcriticalSection对象,当线程需要访问保护数据时,调用临界区对象的Lock()成员函数;当对保护数据的操作完成之后,调用临界区对象的Unlock()成员函数释放对临界区对象的拥有权,以使另一个线程可以夺取临界区对象并访问受保护的数据。同时启动两个线程,它们对应的函数分别为WriteThread()和ReadThread(),用以对公共数组组array[]操作,下面的代码说明了如何使用临界区对象:
#include "afxmt.h" int array[10],destarray[10]; CCriticalSection Section; //////////////////////////////////////////////////////////////////////// UINT WriteThread(LPVOID param) {Section.Lock(); for(int x=0;x<10;x++) array[x]=x; Section.Unlock(); } UINT ReadThread(LPVOID param) { Section.Lock(); For(int x=0;x<10;x++) Destarray[x]=array[x]; Section.Unlock(); } |
上述代码运行的结果应该是Destarray数组中的元素分别为1-9,而不是杂乱无章的数,如果不使用同步,则不是这个结果,有兴趣的读者可以实验一下
2. 互斥
互斥与临界区很相似,但是使用时相对复杂一些,它不仅可以在同一应用程序的线程间实现同步,还可以在不同的进程间实现同步,从而实现资源的安全共享。互斥与Cmutex类的对象相对应,使用互斥对象时,必须创建桓鯟SingleLock或CMultiLock对象,用于实际的访问控制,因为这里的例子只处理单个互斥,所以我们可以使用CSingleLock对象,该对象的Lock()函数用于占有互斥,Unlock()用于释放互斥。实现代码如下:
#include "afxmt.h" int array[10],destarray[10]; CMutex Section; ///////////////////////////////////////////////////////////// UINT WriteThread(LPVOID param) { CsingleLock singlelock; singlelock (&Section); singlelock.Lock(); for(int x=0;x<10;x++) array[x]=x; singlelock.Unlock(); } UINT ReadThread(LPVOID param) { CsingleLock singlelock; singlelock (&Section); singlelock.Lock(); For(int x=0;x<10;x++) Destarray[x]=array[x]; singlelock.Unlock(); } |
3. 信号量
信号量的用法和互斥的用法很相似,不同的是它可以同一时刻允许多个线程访问同一个资源,创建一个信号量需要用Csemaphore类声明一个对象,一旦创建了一个信号量对象,就可以用它来对资源的访问技术。要实现计数处理,先创建一个CsingleLock或CmltiLock对象,然后用该对象的Lock()函数减少这个信号量的计数值,Unlock()反之。下面的代码分别启动三个线程,执行时同时显示二个消息框,然后10秒后第三个消息框才得以显示。
///////////////////////////////////////////////////////////////// Csemaphore *semaphore; Semaphore=new Csemaphore(2,2); HWND hWnd=GetSafeHwnd(); AfxBeginThread(threadProc1,hWnd); AfxBeginThread(threadProc2,hWnd); AfxBeginThread(threadProc3,hWnd); ////////////////////////////////////////////////////////////////////// UINT ThreadProc1(LPVOID param) {CsingleLock singelLock(semaphore); singleLock.Lock(); Sleep(10000); ::MessageBox((HWND)param,"Thread1 had access","Thread1",MB_OK); return 0; } UINT ThreadProc2(LPVOID param) {CSingleLock singelLock(semaphore); singleLock.Lock(); Sleep(10000); ::MessageBox((HWND)param,"Thread2 had access","Thread2",MB_OK); return 0; } UINT ThreadProc3(LPVOID param) {CsingleLock singelLock(semaphore); singleLock.Lock(); Sleep(10000); ::MessageBox((HWND)param,"Thread3 had access","Thread3",MB_OK); return 0; } |
对复杂的应用程序来说,线程的应用给应用程序提供了高效、快速、安全的数据处理能力。本文讲述了线程中经常遇到的问题,希望对读者朋友有一定的帮助。
例程分析多线程编程
Windows系统平台经历了16位到32位的转变后,系统运行方式和任务管理方式有了很大的变化,在Windows 95和Windows NT中,每个Win32程序在独立的进程空间上运行,32位地址空间使我们从16位段式结构的64K段限制中摆脱出来,逻辑上达到了4G的线性地址空间,我们在设计程序时,不再需要考虑编译的段模式,同时还提高了大程序的运行效率。独立进程空间的另一个更大的优越性是大大提高了系统的稳定性,一个应用的异常错误不会影响其它的应用。与在MS-DOS和16位Windows操作系统中不同,32位Windows进程是没有活力的。这就是说,一个32位Windows进程并不执行什么指令,它只是占据着4GB的地址空间,此空间中有应用程序EXE文件的代码和数据。EXE需要的DLL也将它们的代码的数据装入到进程的地址空间。除了地址空间,进程还占有某些资源,比如文件、动态内存分配和线程。当进程终止时,在它生命期中创建的各种资源将被清除。
如上所述,进程是没有活力的,它只是一个静态的概念。为了让进程完成一些工作,进程必须至少占有一线程,所以线程是描述进程内的执行,正是线程负责执行包含在进程的地址空间中的代码。实际上,单个进程可能包含几个线程,它们可以同时执行进程的地址空间中的代码。为了做到这一点,每个线程有自己的一组CPU寄存器和椎。每个进程至少有一个线址程在执行其地址空间中的代码,如果没有线程执行进程地空间中的代码,如果没有线程执行进程地址空间中的代码,进程也就没有继续存在的理由,系统将自动清除进程及其地址空间。为了运行所有这些线程,操作系统为每个独立线程安排一些CPU时间,操作系统以轮转方式向线程提供时间片,这就给人一种假象,好象这些线程都在同时运行。创建一个32位Windows进程时,它的第一个线程称为主线程,由系统自动生成,然后可由这个主线程生成额外的线程,这些线程又可生成更多的线程。
例如,在基于Internet网上的可视电话系统中,同时要进行语音采集、语音编译码、图像采集、图像编译码、语音和图像码流的传输,所有这些工作,都要并行处理。特别是语音信号,如果进行图像编解码时间过长,语音信号得不到服务,通话就有间断;如果图像或语音处理时间过长,而不能及时传输码流数据,通信同样也会中断。这样就要求我们实现一种并行编程,在只有一个CPU的机器上,也就是要将该CPU时间按时一定的优先准则分配给各个事件,定期处理各事件,而不会对某一事件处理过长。
在32位Windows95或Windows NT下,我们可以用多线程的处理技术来实现这种并行处理。实际上,这种并行编程在很多场合下都是必须的。再例如,在File Manager拷贝文件时,它显示一个对话框中包含了一个Cancel按钮。如果在文件拷贝过程中,点中Cance l按钮,就会终止拷贝。在16位Winows中,实现这类功能需要在File Copy循环内部周期性地调用PeekMessage函数。如果正在读一个很大的动作;如果从软盘读文件,则要花费好几秒的时间。由于机器反应太迟钝,用户会频繁地点中这个按钮,以为系统不知道想终止这个操作。如果把File Copy指令放入另外一个线程,就不需要在代码中放一大堆PeekMessage函数,处理用户界面的线程将与它分开操作,点中Cancel按钮后会立即得到响应。同样的道理,在应用程序中创建一个单独线程来处理所有打印任务也是很有用的,用户可以在打印处理时继续使用应用程序。
多线程的编程在Win32方式下和MFC类库支持下的原理是一致的,进程的主线程在任何需要的时候都可以创建新的线程,当线程执行完任务后,自动终止线程,当进程结束后,所有的线程都终止。所有活动的线程共享进程的资源,所以在编程时,需要考虑在多个线程访问同一资源时,产生冲突的问题,当一个线程正在访问一个进程对象,这时另一个线程要改变该对象,这时可能会产生错误的结果,所以程序员在编程时要解决这种冲突。
下面举例说明在Win32 基础上进行多线程编程的过程。
1.使用函数说明
Win32函数库里提供了多线程控制的操作函数,包括创建线程、终止线程、建立互斥区等。首先,在应用程序的主线程或者其它活动线程的适当的地方创建新的线程,创建线程的函数如下:
HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId );
参数lpThreadAttributes 指定了线程的安全属性,在Windows 95中被忽略;
参数dwStackSize 指定了线程的堆栈深度;
参数lpStartAddress 指定了线程的起始地址,一般情况为下面原型的函数
DWORD WINAPI ThreadFunc( LPVOID );
参数 lpParameter指定了线程执行时传送给线程的32位参数,即上面函数的参数;
参数dwCreationFlags指定了线程创建的特性;
参数 lpThreadId 指向一个DWORD变量,可返回线程ID值。
如果创建成功则返回线程的句柄,否则返回NULL。
创建了新的线程后,线程开始启动执行,如果在dwCreationFlags中用了CREATE_SUSPENDED特性,那么线程并不马上执行,而是先挂起,等到调用ResumeThread后才开始启动线程,在这过程中可以调用函数
BOOL SetThreadPriority( HANDLE hThread, int nPriority);
设置线程的优先权。
当线程的函数返回后,线程自动终止,如果要想在线程的执行过程中终止的话,可以调用函数
VOID ExitThread( DWORD dwExitCode);
如果在线程的外面终止线程的话,可以用下面的函数
BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode );
但注意,该函数可能会引起系统不稳定,而且,线程所占用的资源也不释放,因此,一般情况下,建议不要使用该函数。
如果要终止的线程是进程内的最后一个线程的话,在线程被终止后,相应的进程也终止。
2. 无优先级例程,该例程实现在对话框上通过一线程实现时钟的显示和停止。步骤如下:
第一步:建立一基于对话框的工程MultiProcess1。
第二步:在对话框上建立两个按钮和一个编辑框,ID号分别为ID_START、ID_STOP和IDC_TIME,Caption分别为"启动"、"停止"。如下:
![]() |
第三步:在MultiProcess1Dlg.cpp中定义全局函数ThreadProc(),格式如下:
void ThreadProc() { CTime time; CString m_time; for(;;) { time=CTime::GetCurrentTime(); m_time=time.Format("%H:%M:%S"); ::SetDlgItemText(AfxGetMainWnd()->m_hWnd,IDC_TIME,m_time); Sleep(1000); } } |
第四步:在头文件MultiProcess1Dlg.h中定义变量如下:
DWORD ThreadID;
HANDLE hThread;
第五步:双击"开始"按钮,生成消息映射函数OnStart(),编写其中的代码如下:
void CMultiProcess1Dlg::OnStart() { // TODO: Add your control notification handler code here hThread=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc, NULL,0,&ThreadID); } |
此时即刻实现在对话框上点击"启动",启动时钟。接下来我们实现如何让时钟停下来。
第六步:双击"停止"按钮,添加停止的消息映射函数OnStop(),编写代码如下:
void CMultiProcess1Dlg::OnStop() { // TODO: Add your control notification handler code here TerminateThread(hThread,1); } |
注意:该函数可能会引起系统不稳定,而且,线程所占用的资源也不释放,因此,一般情况下,建议不要使用该函数。
到现在,这个程序就完整了,看一下效果吧!
最后需要说明的是,并不是设计多线程就是一个好的程序。目前大多数的计算机都是单处理器(CPU)的,在这种机器上运行多线程程序,有时反而会降低系统性能,如果两个非常活跃的线程为了抢夺对CPU的控制权,在线程切换时会消耗很多的CPU资源,但对于大部分时间被阻塞的线程(例如等待文件 I/O 操作),可以用一个单独的线程来完成,这样可把CPU时间让出来,使程序获得更好的性能。因此,在设计多线程应用时,需要慎重选择,具体情况具体处理,以使应用程序获得最佳的性能。
用VC++5.0实现多线程的调度和处理
一多任务,多进程和多线程
---- Windows95 和WindowsNT 操作系统支持多任务调度和处理,基于该功能所提供的多任务空间,程序员可以完全控制应用程序中每一个片段的运行,从而编写高效率的应用程序。
---- 所谓多任务通常包括这样两大类:多进程和多线程。进程是指在系统中正在运行的一个应用程序;线程是系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元。对于操作系统而言,其调度单元是线程。一个进程至少包括一个线程,通常将该线程称为主线程。一个进程从主线程的执行开始进而创建一个或多个附加线程,就是所谓基于多线程的多任务。
---- 开发多线程应用程序可以利用32 位Windows 环境提供的Win32 API 接口函数,也可以利用VC++ 中提供的MFC 类库进行开发。多线程编程在这两种方式下原理是一样的,用户可以根据需要选择相应的工具。本文重点讲述用VC++5.0 提供的MFC 类库实现多线程调度与处理的方法以及由线程多任务所引发的同步多任务特征,最后详细解释一个实现多线程的例程。
二基于MFC 的多线程编程
---- 1 MFC 对多线程的支持
---- MFC 类库提供了多线程编程支持,对于用户编程实现来说更加方便。非常重要的一点就是,在多窗口线程情况下,MFC 直接提供了用户接口线程的设计。
---- MFC 区分两种类型的线程:辅助线程(Worker Thread)和用户界面线程(UserInterface Thread)。辅助线程没有消息机制,通常用来执行后台计算和维护任务。MFC 为用户界面线程提供消息机制,用来处理用户的输入,响应用户产生的事件和消息。但对于Win32 的API 来说,这两种线程并没有区别,它只需要线程的启动地址以便启动线程执行任务。用户界面线程的一个典型应用就是类CWinApp,大家对类CwinApp 都比较熟悉,它是CWinThread 类的派生类,应用程序的主线程是由它提供,并由它负责处理用户产生的事件和消息。类CwinThread 是用户接口线程的基本类。CWinThread 的对象用以维护特定线程的局部数据。因为处理线程局部数据依赖于类CWinThread,所以所有使用MFC 的线程都必须由MFC 来创建。例如,由run-time 函数_beginthreadex 创建的线程就不能使用任何MFC API。
---- 2 辅助线程和用户界面线程的创建和终止
---- 要创建一个线程,需要调用函数AfxBeginThread。该函数通过参数重载具有两种版本,分别对应辅助线程和用户? 线程。无论是辅助线程还是用户界面线程,都需要指定额外的参数以修改优先级,堆栈大小,创建标志和安全特性等。函数AfxBeginThread 返回指向CWinThread 类对象的指针。
---- 创建助手线程相对简单。只需要两步:实现控制函数和启动线程。它并不必须从CWinThread 派生一个类。简要说明如下:
---- 1. 实现控制函数。控制函数定义该线程。当进入该函数,线程启动;退出时,线程终止。该控制函数声明如下:
UINT MyControllingFunction( LPVOID pParam );
---- 该参数是一个单精度32 位值。该参数接收的值将在线程对象创建时传递给构造函数。控制函数将用某种方式解释该值。可以是数量值,或是指向包括多个参数的结构的指针,甚至可以被忽略。如果该参数是指结构,则不仅可以将数据从调用函数传给线程,也可以从线程回传给调用函数。如果使用这样的结构回传数据,当结果准备好的时候,线程要通知调用函数。当函数结束时,应返回一个UINT 类型的值值,指明结束的原因。通常,返回0 表明成功,其它值分别代表不同的错误。
---- 2. 启动线程。由函数AfxBeginThread 创建并初始化一个CWinThread 类的对象,启动并返回该线程的地址。则线程进入运行状态。
---- 3. 举例说明。下面用简单的代码说明怎样定义一个控制函数以及如何在程序的其它部分使用。
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);
……
创
声明:该文观点仅代表作者本人,牛骨文系教育信息发布平台,牛骨文仅提供信息存储空间服务。
- 上一篇: el表达式string强制转换成long报错
- 下一篇: jstl与EL表达式处理字符串