还在Dos时代,人们就在寻求一种多任务的实现。于是出现了TSR类型的后台驻留程序,比较有代表性的有Side Kick、Vsafe等优秀的TSR程序,这类程序的出现和应用确实给用户使用计算机带来了极大的方便,比如Side Kick,我们编程可以在不用进编辑程序的状态下,一边编辑源程序,一边编译运行,非常方便。但是,Dos单任务操作系统的致命缺陷注定了在Dos下不可能开发出真正的多任务程序。进入Windows3.1时代,这种情况依然没有根本的改变,一次应用只能做一件事。比如数据库查询,除非应用编得很好,在查询期间整个系统将不响应用户的输入。
进入了Windows NT和Windows 9x时代,情况就有了彻底的改观,操作系统从真正意义上实现了多任务(严格地说,Win9x还算不上)。一个应用程序,在需要的时候可以有许多个执行线程,每个线程就是一个小的执行程序,操作系统自动使各个线程共享CPU资源,确保任一线程都不能使系统死锁。这样,在编程的时候,可以把费时间的任务移到后台,在前台用另一个线程接受用户的输入。对那些对实时性要求比较高的编程任务,如网络客户服务、串行通信等应用时,多线程的实现无疑大大地增强了程序的可用性和稳固性。
在Windows NT和Windows 9x中,多线程的编程实现需要调用一系列的API函数,如CreateThread、ResumeThread等,比较麻烦而且容易出错。我们使用Inprise公司的新一代RAD开发工具C++Builder,可以方便地实现多线程的编程。与老牌RAD工具Visual Basic和Delphi比,C++Builer不仅功能非常强大,而且它的编程语言是C++,对于系统开发语言是C的Windows系列操作系统,它具有其它编程语言无可比拟的优势。利用C++Builder提供的TThread对象,多线程的编程变得非常简便易用。那么,如何实现呢?且待我慢慢道来,让你体会一下多线程的强大功能。
1. 创建多线程程序:
首先,先介绍一下实现多线程的具体步骤。在C++Builder中虽然用Tthread对象说明了线程的概念,但是Tthread对象本身并不完整,需要在TThread下新建其子类,并重载Execute方法来使用线程对象。在C++Builder下可以很方便地实现这一点。
在C++Builder IDE环境下选择菜单FileNew,在New栏中选中Thread Object,按OK,接下来弹出输入框,输入TThread对象子类的名字MyThread,这样C++Builder自动为你创建了一个名为TMyThread的TThread子类。同时编辑器中多了一个名为Unit2.cpp的单元,这就是我们创建的TMyThread子类的原码,如下:
#include
#pragma hdrstop
#include “Unit2.h”
#pragma package(smart_init)
//---------------------
// Important: Methods and properties of objects in VCL can only be
// used in a method called using Synchronize, for example:
//
// Synchronize(UpdateCaption);
//
// where UpdateCaption could look like:
//
// void __fastcall MyThread::UpdateCaption()
// {
// Form1->Caption = “Updated in a thread”;
// }
//--------------------
__fastcall MyThread::MyThread(bool CreateSuspended)
: TThread(CreateSuspended)
{
}
//--------------------
void __fastcall MyThread::Execute()
{
//---- Place thread code here ----
}
//---------------------
其中的Execute()函数就是我们要在线程中实现的任务的代码所在处。在原代码中包含Unit2.cpp,这个由我们创建的TMyThread对象就可以使用了。使用时,动态创建一个TMyThread 对象,在构造函数中使用Resume()方法,那么程序中就增加了一个新的我们自己定义的线程TMyThread,具体执行的代码就是Execute()方法重载的代码。要加载更多的线程,没关系,只要继续创建需要数量的TMyThread 对象就成。
以上我们初步地实现了在程序中创建一个自定义的线程,并使程序实现了多线程应用。但是,多线程应用的实现,并不是一件简单的工作,还需要考虑很多使多个线程能在系统中共存、互不影响的因素。比如,程序中公共变量的访问、资源的分配,如果处理不当,不仅线程会死锁陷入混乱,甚至可能会造成系统崩溃。总的来讲,在多线程编程中要注意共享对象和数据的处理,不能忽视。因此,下面我们要讲的就是多线程中常见问题:
2. 多线程中VCL对象的使用
我们都知道,C++Builder编程是建立在VCL类库的基础上的。在程序中经常需要访问VCL对象的属性和方法。不幸的是,VCL类库并不保证其中对象的属性和方法是线程访问安全的(Thread_safe),访问VCL对象的属性或调用其方法可能会访问到不被别的线程所保护的内存区域而产生错误。因此,TThread对象提供了一个Synchronize方法,当需要在线程中访问VCL对象属性或调用方法时,通过Synchronize方法来访问属性或调用方法就能避免冲突,使各个线程之间协调而不会产生意外的错误。如下所示:
void __fastcall TMyThread::PushTheButton(void)
{
Button1->Click();
}
void __fastcall TMyThread::Execute()
{
...
Synchronize((TThreadMethod)PushTheButton);
...
}
对Button1-〉Click()方法的调用就是通过Synchronize()方法来实现的,它可以自动避免发生多线程访问冲突。在C++Builder中,虽然有一些VCL对象也是线程访问安全的(如TFont、TPen、TBrush等),可以不用Sychronize()方法对它们的属性方法进行访问调用以提高程序性能,但是,对于更多的无法确定的VCL对象,还是强烈建议使用Synchronize()方法确保程序的可靠性。
3. 多线程中公共数据的使用
程序设计中难免要在多个线程中共享数据或者对象。为了避免在多线程中因为同时访问了公共数据块而造成灾难性的后果,我们需要对公共数据块进行保护,直到一个线程对它的访问结束为止。这可以通过临界区域(Critical Section)的使用来实现,所幸的是在C++Builder中,给我们提供了一个TCriticalSection对象来进行临界区域的划定。该对象有两个方法,Acquire()和Release()。它设定的临界区域可以保证一次只有一个线程对该区域进行访问。如下例所示:
class MyThread : public TThread
{
...
private:
TCriticalSection pLockX;
int x;
float y;
...
};
void __fastcall MyThread::Execute()
{
...
pLockX->Acquire();//Here pLockX is a Global CriticalSection variable.
x++;
y=sin(x);
pLockX->Release();
...
}
这样,对公共变量x,y的访问就通过全局TCriticalSection 对象保护起来,避免了多个线程同时访问的冲突。
4. 多线程间的同步
当程序中多个线程同时运行,难免要遇到使用同一系统资源,或者一个线程的运行要依赖另一个线程的完成等等,这样需要在线程间进行同步的问题。由于线程同时运行,无法从程序本身来决定运行的先后快慢,使得线程的同步看起来很难实现。所幸的是Windows系统是多任务操作系统,系统内核为我们提供了事件(Event)、Mutex、信号灯(semaphore)和计时器4种对象来控制线程间的同步。在C++Builder中,为我们提供了用于创建Event的TEvent 对象供我们使用。
当程序中一个线程的运行要等待一项特定的操作的完成而不是等待一个特定的线程完成时,我们就可以很方便地用TEvent对象来实现这个目标。首先创建一个全局的TEvent对象作为所有线程可监测的标志。当一个线程完成某项特定的操作时,调用TEvent对象的SetEvent()方法,这样将设置这个标志,其他的线程可以通过监测这个标志获知操作的完成。相反,要取消这个标志,可以调用ResetEvent()方法。在需要等待操作完成的线程中使用WaitFor()方法,将一直等待这个标志被设置为止。注意WaitFor()方法的参数是等待标志设置的时间,一般用INFINITE表示无限等待事件的发生,如果其它线程运行有误,很容易使这个线程死住(等待一个永不发生的事件)。
其实直接用Windows API函数也可以很方便地实现事件(Event)、信号灯(semaphore)控制技术。尤其是C++Builder,在调用Windows API方面有着其它语言无可比拟的优势。所用的函数主要有:CreateSemaphore()、CreateEvent()、WaitForSingleObject()、ReleaseSemaphore()、SetEvent()等等,这里就不赘述了。
本文结合Inprise(Borland)公司开发的强大的RAD工具C++Builder的编程,对Windows下的多线程编程作了比较全面的介绍。其实多线程的实现并不神秘,看了本文,你也可以编出自己的多线程程序,真正体会多任务操作系统的威力。
附:本文是本人在使用C++Builder一年来的一些实践体会。在完成自己的项目的同时,发现对多线程的编程一般的书籍都介绍得比较少,而实际应用中,多线程编程又是如此的重要,因此,本文通过对多线程编程比较全面的介绍,愿能达到抛砖引玉之效。