基金项目要保留_论文发表__墨水学术,论文发表,发表论文,职称论文

所属栏目:项目管理论文范文发布时间:2011-02-25浏览量:166

副标题#e#
  基金项目要保留
  Delphi多线程编程中同步机制的分析与研究
  胡丹桂
  武汉职业技术学院计算机学院湖北武汉430074
  基金项目:
  湖北省教育厅2008年度人文社会科学研究项目重点项目编号2008d154
  项目名称:高等职业院校《Delphi程序设计》教学改革研究
  项目主持人武汉职业技术学院计算机学院何定华
  摘要:Delphi封装了一个类:TThread,使用这个类可以很方便的实现多线程编程。多线程编程可以带来很多好处,但同时也存在不少问题,线程同步就是多线程编程中一个非常关键及重要的问题。Delphi提供了多种同步对象来完成线程的同步,下面就Delphi多线程编程中如何实现同步机制来进行分析与研究。
  关键字:线程同步Synchronize临界区
  1概述
  线程本质上是进程中一段并发运行的代码。一个进程至少有一个线程,即所谓的主线程。同时还可以有多个子线程。当一个进程中用到超过一个线程时,就是所谓的“多线程”。Delphi在Classes单元中对线程作了一个较好的封装,这就是VCL的线程类:TThread。这个类基本用法是:先从TThread派生一个自己的线程类(因为TThread是一个抽象类,不能生成实例),然后是Override抽象方法:Execute(这就是线程函数,也就是在线程中执行的代码部分)。
  多线程同步工作时,如果同时调用相同的资源,就可能会出现问题,一般读出是不会有问题的,但是,如果写入(全局变量、数据库),就会发生冲突,甚至产生死锁和竞争问题。
  以加一为例:
  一般来说,对内存数据加一的操作分解以后有三个步骤:
  1、从内存中读出数据
  2、数据加一
  3、存入内存
  现在假设在一个两个线程的应用中进行加一操作可能出现的一种情况:
  1、线程A从内存中读出数据(假设为3)
  2、线程B从内存中读出数据(也是3)
  3、线程A对数据加一(现在是4)
  4、线程B对数据加一(现在也是4)
  5、线程A将数据存入内存(现在内存中的数据是4)
  6、线程B也将数据存入内存(现在内存中的数据还是4,但两个线程都对它加了1,应该是5才对,所以这里出现了错误的结果)。
  由此可见,多线程编程中研究多线程的同步机制就显得非常重要。
  2同步机制
  同步机制,实际上是事件驱动机制,意思是让线程平时处于“休眠”状态,除非发生某个事件才触发。线程的同步包括许多方面的内容,Delphi和Windows提供了许多方法,可以非常容易地实现线程的同步。
  2.1使用Synchronize方法
  在Delphi的多线程编程中,各种VCL构件都是临界资源,只能由主线程使用。其它线程要使用这些VCL构件,必须使用Synchronize方法,通过传递使用了VCL构件的方法,可避免多线程与VCL构件的冲突,避免重入问题。该过程带有唯一一个TThreadMethod类型的参数是一个不接收参数的对象方法,用于指定在线程对象中的方法。
  其方法的应用是:
  第一步:把访问主窗口(或主窗口控件资源)的代码放到线程的一个方法中;
  第二步:是在线程对象的Execute方法中,通过Synchronize方法使用该方法。
  实例:
  procedureTThreadMethodObject.Execute;
  begin
  Synchronize(update);
  end;
  procedureTThreadMethodObject.update;
  begin
  .........
  end;
  这里通过Synchronize使线程方法update同步。
  2.2使用VCL类的Lock方法
  在Delphi的IDE提供的构件中,有一些对象内部提供了线程的同步机制,工作线程可以直接使用这些控件,比如:Tfont,Tpen,TBitmap,TMetafile,Ticon等。另外,一个很重要的控件对象叫TCanvas,提供了一个Lock方法用于线程的同步,当一个线程使用此控件对象的时候,首先调用这个对象的Lock方法,然后对这个控件进行操作,完毕后再调用Unlock方法,释放对控间的控制#p#副标题#e#权。
  例如:
  CanversObject.lock;
  try
  画图
  finally
  CanversObject.unlock;
  end;
  使用这个保护机制,保证不论有没有异常,unlock都会被执行否则很可能会发生死锁。在多线程设计的时候,应该很注意发生死锁的问题。
  2.3使用Waitfor方法
  WaitFor的功能是检查Event的状态是否是Set状态(相当于True),如果是则立即返回,如果不是,则等待它变为Set状态,在等待期间,调用WaitFor的线程处于挂起状态。另外WaitFor有一个参数用于超时设置,如果此参数为0,则不等待,立即返回Event的状态,如果是INFINITE则无限等待,直到Set状态发生,若是一个有限的数值,则等待相应的毫秒数后返回Event的状态。
  当Event从Reset状态向Set状态转换时,唤醒其它由于WaitFor这个Event而挂起的线程,这就是它为什么叫Event的原因。所谓“事件”就是指“状态的转换”。通过Event可以在线程间传递这种“状态转换”信息。
  Waitfor方法的原型如下:FunctionWaitfor(ConstAstring:string):string;
  比如唤醒线程的语句:
  thread1.resume;
  thread1.waitfor;
  thread2.resume;
  那么所有的线程都必须等待thread1运行完毕后才能运行,其中包括主线程,可以预想,由于thread1调用了主窗体的Edit控件,那么,在thread1运行中间,Edie1也不会显示。
  这就告诉我们,这样的代码是不能作为主线程的一部分的,如果与主窗体连接的线程内等待另一个线程结束,而另一个线程又要等待访问用户界面,就可能是程序陷于死锁。
  2.4临界区
  临界区(CriticalSection)则是一项共享数据访问保护的技术。它其实也是相当于一个全局的布尔变量。但对它的操作有所不同,它只有两个操作:Enter和Leave,同样可以把它的两个状态当作True和False,分别表示现在是否处于临界区中。这两个操作也是原语,所以它可以用于在多线程应用中保护共享数据,防止访问冲突。
  用临界区保护共享数据的方法很简单:在每次要访问共享数据之前调用Enter设置进入临界区标志,然后再操作数据,最后调用Leave离开临界区。它的保护原理是这样的:当一个线程进入临界区后,如果此时另一个线程也要访问这个数据,则它会在调用Enter时,发现已经有线程进入临界区,然后此线程就会被挂起,等待当前在临界区的线程调用Leave离开临界区,当另一个线程完成操作,调用Leave离开后,此线程就会被唤醒,并设置临界区标志,开始操作数据,这样就防止了访问冲突。
  再来看前面那个例子:
  1.线程A进入临界区(假设数据为3)
  2.线程B进入临界区,因为A已经在临界区中,所以B被挂起
  3.线程A对数据加一(现在是4)
  4.线程A离开临界区,唤醒线程B(现在内存中的数据是4)
  5.线程B被唤醒,对数据加一(现在就是5了)
  6.线程B离开临界区,现在的数据就是正确的了。
  关于临界区的使用,有一点要注意:即数据访问时的异常情况处理。因为如果在数据操作时发生异常,将导致Leave操作没有被执行,结果将使本应被唤醒的线程未被唤醒,可能造成程序的没有响应。所以一般来说,临界区正确的做法:
  EnterCriticalSection
  Try
  //操作临界区数据
  Finally
  LeaveCriticalSection
  End;
  最后,CriticalSection是操作系统资源,使用前需要创建,使用完后也需要释放。如TThread类用到的一个全局CriticalSection:TheadLock,是在InitThreadSynchronization和DoneThreadSynchronization中进行创建和释放的,而它们则是在Classes单元的Initialization和Finalization中被调用的。
  3总结
  因此,在Delphi多线程编程中,对可视VCL的访问要放在Synchronize中,通过消息传递到主线程中,由主线程处理。线程共享数据的访问应该用临界区进行保#p#副标题#e#护。线程通信可以采用Event进行(当然也可以用Suspend/Resume)。当在多线程应用中使用多种线程同步方式时,一定要小心防止出现死锁。等待线程结束要用WaitFor方法。
期刊 论文 出书
国内外/中英文/全学科 学术服务
相关阅读