[转]李战大师-悟透delphi-第三章 多线程

第三章          多线程

古时候,有一位刚刚出道的的骑士去到牧马场挑选一匹好马。在马房和牧马人聊天的时候,他大吹特吹自己驾驭马匹的高超技能。牧马人听完他的唠叨之后说:“请你将草原上吃草的那群马引进马房,我送你一匹最好的马!”。击掌为誓之后,骑士拿起长鞭骑马出去了。过了很久,那个骑士汗流满面灰溜溜地回来了。这时牧马人语重心长地对他说:“能驾驭一匹马不一定可以驾驭一群马,你在马背上的前程还长着呢!”。骑士听了之后羞愧满面。多年以后,这位骑士成为了一位领兵打仗的将军,驰骋沙场为国家立了不少战功。
能驾驭一匹马不一定可以驾驭一群马,会编写单线程的程序不一定会编写多线程的程序。编写多线程的程序就像驾驭一群马一样,要想每一匹马都能听你的话,可真是要下点功夫才行。
第一节 了解线程
线程就是程序执行的动态线索。和进程的概念一样,线程也是动态的。不过进程的概念偏向进程空间的动态性,而线程的概念更关注程序执行线索的动态性。
前面我们说过,推动进程运转的是线程。因为,Windows操作系统是以进程为单位来安排系统的空间,却以线程为单位来分配CPU的时间片。
操作系统在加载和执行一个程序时,首先为程序建立一个进程提供进程空间,然后再为程序建立一个线程以分配CPU时间片,推动程序的运行。这个由操作系统初始建立的线程就称为进程的主线程。
你发出运行程序的命令给操作系统,操作系统便安排内存空间等系统资源,并立即指派一个线程牵头去完成任务。这个牵头的线程可以独立完成你的任务,也可以再指派其他的线程协助完成任务。如果一个线程还指派了其他的线程一起来执行程序,这就是所谓的多线程。一个线程可以再指派一个或多个子线程,这些线程共同协作完成最终的任务。这就好像部门领导接到工作任务,他一般会在自己工作的同时安排任务给下属人员完成一样。当然,下属人员又可以安排任务给再下属的人员。这就是说,子线程还可产生它自己的子线程。
我很少编写多线程的程序,一般都是在前人搭建好的多线程的程序体系中,小心控制程序资源的共享和互斥问题。我想可能许多朋友都和我一样,很少自己去使用CreateThread函数或者用TThread类去建立一个线程。例如,在编写多层应用程序的服务器端对象时,常常需要选择一种线程模式,之后你只需要在程序里考虑在多线程情况下要注意的问题。DELPHI已经帮你搭建好了多线程的体系结构,只是你要清楚地认识到你正在编写多线程的程序。
在DELPHI的调试状态下,你是可以观察线程状态的。打开View\Debug Windows\Threads菜单可调出Thread Status窗口,在此可了解当前调试的程序有多少个线程以及它们的状态。下面是一个小程序,很简单,看起来绝对不是一个多线程的程序。但它使用了TAnimate控件,而一个TAnimate是用线程来实现的。写这个程序的目的主要是让你了解怎样用DELPHI的Thread Status窗口察看程序线程的变化。
项目文件AnimateThread.dpr:

program AnimateThread;

uses
  Forms,
  AnimateThreadUnit in 'AnimateThreadUnit.pas' {fAnimateThread};

{$R *.RES}

begin
  Application.Initialize;
  Application.CreateForm(TfAnimateThread, fAnimateThread);
  Application.Run;
end.

单元文件AnimateThreadUnit.pas:

unit AnimateThreadUnit;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ComCtrls, StdCtrls;

type
  TfAnimateThread = class(TForm)
    aniFindComputer: TAnimate;
    aniFindFile: TAnimate;
    chkFindComputer: TCheckBox;
    chkFindFile: TCheckBox;
    procedure chkFindComputerClick(Sender: TObject);
    procedure chkFindFileClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  fAnimateThread: TfAnimateThread;

implementation

{$R *.DFM}

procedure TfAnimateThread.chkFindComputerClick(Sender: TObject);
begin
  aniFindComputer.Active:=chkFindComputer.Checked;
end;

procedure TfAnimateThread.chkFindFileClick(Sender: TObject);
begin
  aniFindFile.Active:=chkFindFile.Checked;
end;

end.

窗体文件AnimateThreadUnit.dfm:

object fAnimateThread: TfAnimateThread
  Left = 578
  Top = 112
  Width = 187
  Height = 96
  Caption = '观察动画线程'
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object aniFindComputer: TAnimate
    Left = 16
    Top = 12
    Width = 16
    Height = 16
    Active = False
    CommonAVI = aviFindComputer
    StopFrame = 8
  end
  object aniFindFile: TAnimate
    Left = 16
    Top = 36
    Width = 16
    Height = 16
    Active = False
    CommonAVI = aviFindFile
    StopFrame = 8
  end
  object chkFindComputer: TCheckBox
    Left = 80
    Top = 12
    Width = 89
    Height = 17
    Caption = 'Find Computer'
    TabOrder = 2
    OnClick = chkFindComputerClick
  end
  object chkFindFile: TCheckBox
    Left = 80
    Top = 36
    Width = 89
    Height = 17
    Caption = 'Find File'
    TabOrder = 3
    OnClick = chkFindFileClick
  end
end

在DELPHI开发环境中运行这个程序,然后打开Thread Status窗口。这时,Thread Status窗口中只有一个线程的状态,那就是主线程。当你分别启动或停止程序窗口中的两个动画时,你会看到Thread Status窗口中会有另外两个线程产生或消失。如下图所示:
这个程序还让我们明白,我们在编写程序时有可能在不经意的情况下涉及了多线程。因为我们常用的一些控件可能就是用线程实现的,例如TAnimate。所以,多线程的程序可能无处不在,多了解和学习多线程的知识可以使我们在编程中少犯错误。

要编好多线程的程序,关键是控制好各线程之间的协同运作。说起来容易做起来难,协同运作可不那么容易。
我们知道,在Windows操作系统中,每一个进程有自己独立的进程空间。操作系统将运行的每一个进程严格的分隔开,进程间不会相互干扰。但进程中的多个线程就不是这样了,他们处在同一个生存空间之中,共享着进程空间的各种资源。如果不能有效地管束你创建的多个线程,他们就会为了争夺有限的进程资源而互相拼杀。一个线程好不容易计算出一组数据,另一个线程又将其改掉;一个线程刚打开一个文件,另一个线程立刻写了一些自己的意见到文件中;一个线程正在打印数据,而另一个线程中途插进一段它的表格。一切都乱了,最后的结果就是让你的整个程序死掉。
进程中的多个线程共享着进程的各种资源,但线程也不完全是一无所有,他也有自己的私有财产。在创建每一个线程的时候,操作系统也会在进程空间中为他划定一些自留地,让他拥有独立堆栈空间、独立的线程数据空间、独立的窗口消息队列和独立的异常处理链。一般来说,这些自留地是线程可以自己掌管的,不用担心其他线程来抢夺(除非其他线程不小心侵犯了这些自留地)。
对于共享的进程资源的使用,操作系统为我们提供了多种管理设备,包括临界区、互斥元、信号量和事件。你可以象使用隔离栏和交通信号灯控制马路上穿梭的车流一样,控制进程中的多个线程有序地运行。但关键是要制定好你的游戏规则,有了这么多管理设备不见得就能管好线程。
第二节 结构化异常处理
在这个世界上,没有人能够一点错误都不犯。再精明的人总又想不到的东西,所谓智者千虑终有一失,老虎也有打盹的时候。但关键是要看在遇到问题之后应怎样处理这些问题。
在结构化异常处理的思想和技术诞生以前,程序员们对程序运行中可能发生的各种异常一直没有一个比较好的方法。尽管,结构化的程序设计技术已经成熟并流行了许多年了。但在那时,即使是编写得最优秀的结构化程序,在遇到未想到的异常错误时,都有可能崩溃。异常错误处理就象一只拦路虎,阻碍程序员向更稳定可靠的程序前进。
那时候,程序员用得比较多异常错误处理方式就是:低层的程序尽可能多地判断所有可能发生的异常错误,并将发现的错误以错误状态的形式返回给上层程序。而上层的程序会针对返回的不同错误状态,进行相关的错误处理。这样,每一层的程序代码都会考虑下层程序会有些什么错误产生并且应该如何处理这些错误,而本层程序产生的错误又应该怎样通知到上一层的程序。最终,一个稳定可靠的优秀程序的代码就包含层层嵌套的if…then…else语句或case语句。在众多的逻辑判断分支程序中,只有一条分支是真正用得到的正确处理过程。于是,编写对异常错误处理的代码就远远多过对正确逻辑的处理代码。
想一想,当你好不容易才设计了一种复杂而又绝妙的处理问题的算法,可你又不得不在编写这一核心算法代码的同时,编写处理各种异常错误的代码,而且这些错误处理代码比核心算法的代码还要多!
有什么办法呢?那时我们就是这样熬过来的!几天几夜熬下来之后,终于可以欣慰地说:我的程序终于能运行了!
现在好了,有了结构化异常处理的思想和方法。这种思想和方法可以让你以一种标准的,也是机械的,当然也是合理的模式对待和处理各种异常错误。这种结构化错误处理思想也是非常简单的,基本符合人们对待日常错误的习惯做法。
比如,当上级领导给下属人员安排任务的时候,上级领导会要求下属人员尽力去完成任务。在大多数情况下任务是可以完成的,但上级领导必须考虑到如果下属人员不能完成任务应该怎么办。上级领导在处理不能完成任务的情况时,可以不必关心为什么不能完成任务而做出果断的处理,也可以弄清问题的具体原因而妥善解决。下属在执行任务中,由于某种原因而不能完成时,只需将问题抛给上级领导就可休息了。
结构化的异常错误处理方式要求调用中的每一层程序必须考虑两个问题,一是管好下层程序,二是对上层程序负责。不出问题则矣,一出问题就应该决定那些问题是本层程序必须处理,哪些问题是要提交高层程序处理,以确保职责分明。其实,我们在工作中不就是这样吗:管好下属人员,对高层领导负责,各尽其职,各担其责。
DELPHI中的结构化异常处理是用三种语句实现。
第一种是异常处理语句:
try
  …… //执行任务代码
except
  …… //异常处理代码
end
其执行流程是:执行try到except之间的代码,如果这些代码都执行成功,则执行end之后的其他代码;如果在try到except之间的代码时发生任何想象不到的异常错误时,将立刻转向except到end之间的代码进行异常处理,处理异常之后才接着执行end之后的其他代码。
第二种是异常保护语句:
…… //任务准备代码
try
  …… //任务执行代码
finally
  …… //异常保护代码
end
其执行流程是:在try之前的代码准备好执行所需的各种资源;然后开始执行try到finally之间的代码;不管try到finally之间的代码是否执行成功,都将执行finally到end之间的代码,以确保归还或释放前面准备的各种资源。如果,try到finally之间的代码执行成功,则执行完finally到end之间的代码之后,继续执行end之后的其他语句;否则,执行完finally到end之间的代码之后,异常将提交上层程序去处理错误。
第三种时异常产生语句:
raise ……
当程序判断出无法继续完成任务时,就产生一个异常。这时,raise语句之后的代码将不再执行,立刻转向上层的异常处理代码或异常保护代码。
结构化异常处理是可以嵌套的。“结构化”一词的含义就是从大处看是同一种结构,从细处看也是同一种结构;高层是这种结构,低层也是这种结构。也就是说,在try到except之间、except到end之间、try到finally之间、finally到end之间的代码中有可以嵌入另一个try…except…end或try…finally…end语句。这种嵌套不仅可以存在于同一过程或函数中,也可以上下跨越过程或函数的调用层次之间。
其实,DELPHI编写的整个应用程序的代码就是处在一个巨大的异常处理块之中的!一旦程序中出现的异常没有任何代码来处理,最终是由系统的异常处理代码处理的,结果就是中止应用程序。说得更深入一点,Windows中的任何线程的执行都是在一个操作系统提供的异常处理块之中!任何未被线程代码处理的异常最终由操作系统接管,结果就是中止和释放线程!
要知道,结构化异常处理是由操作系统来支持的,各种编程语言只是在此基础上描述自己的实现语法。结构化异常处理的控制流程和程序正常的控制流程是两套相对独立的控制流程。正常程序对调用层次的控制只需要堆栈机制就可以了,而结构化异常处理对层次的控制不仅需要堆栈,而且还需要一种称之为“结构化异常处理链”的数据结构。Windows操作系统在创建线程的时候,为每一个线程建立一个“结构化异常处理链”,顶层链头上的异常处理就是中止和释放线程。我们又不是操作系统的专家,没有必要去研究操作系统是怎样实现神秘的结构化异常处理的。只要你知道有这么回事,就行了。
通常,try…except…end语句可以用在这些地方(我能想到的):
1. 将错误信息反馈给用户。例如:
try
  Memo1.Lines.LoadFromFile('A:\README.TXT'); //可能发生问题的处理程序
except
  ShowMessage('读取文件A:\README.TXT出错!'); //将错误情况反馈给用户。
end;

2. 对程序算法中的未知错误情况提供缺省值或经验值。例如:
try
  ProfitRate:=(Income – Payout)/Income; //可能发生问题(Income=0时)的算法。
except
  ProfitRate:=0.0; //返回缺省值或经验值。
end;

3. 出现程序错误时清除执行中的中间状态。例如:
Database.StartTransaction; //启动数据库事务。
try
  …… //可能发生冲突的数据库修改代码。
  Database.Commit;
except
  Database.Rollback; //撤销数据库事务,清除中间状态。
  raise; //再由上层去处理异常。
end;
通常,try…finally…end语句只用于资源的保护,我还没有想到其他的使用方法。例如:
aFile := FileOpen('LOG.DAT', fmOpenReadWrite); //打开文件
try
  ...... //使用文件操作代码。
finally
  FileClose(aFile); //无论如何都要关闭文件。
end;
再如:
aDialog := TMyDialog.Create(nil); //建立对话框对象。
try
  …… //使用对话框。
finally
  aDialog.Free; //无论如何都要释放对话框对象。
end;
值得注意的是,如果程序用到多个资源时,你应该为每一个资源构造一个单独的资源保护结构代码,形成try…finally…end的嵌套结构。例如:
aFile := FileOpen('LOG.DAT', fmOpenReadWrite); //打开文件
try
……
aDialog := TMyDialog.Create(nil); //建立对话框对象。
try
        …… //使用对话框。
finally
        aDialog.Free; //无论如何都要释放对话框对象。
end;
...... //使用文件操作代码。
finally
  FileClose(aFile); //无论如何都要关闭文件。
end;
千万别偷懒写成:
aFile := FileOpen('LOG.DAT', fmOpenReadWrite); //打开文件
aDialog := TMyDialog.Create(nil); //建立对话框对象。
try
  ...... //使用文件和对话框操作代码。
finally
  aDialog.Free; //无论如何都要释放对话框对象。
  FileClose(aFile); //无论如何都要关闭文件。
end;
因为,如果在文件打开之后,创建对话框发生异常,则打开的文件将不会关闭。
烦!真的很烦!最后你会发现一个非常稳定的异常处理嵌套可能是这样的:

try
  ......
  try
    ......
    try
      ......
    except
      ......
    end;
    ......
  finally
    ......
    try
      ......
    except
      ......
    end;
    ......
  end;
  ......
except
  ......
  try
    ......
  except
    ......
    raise;
  end;
  ......
end;
其复杂的嵌套逻辑结构一点都不亚于早期处理错误的if…then..else和case的复杂嵌套结构!
也许,世界上的事情就是这样的吧,几经轮回总有相似的一幕。山林中的风声雨声,溪水流淌瀑布飞溅,鸟鸣兽吼,各种声音都是复杂的。但一曲《高山流水》同样描述这些复杂的风声水声和花香鸟语,却是悦耳动听。无序的声音是杂乱无章的噪音,而有序的声音便是和谐而美妙的音乐!
结构化异常错误处理虽然也很复杂,但却是有序的。它已经有了本质的飞跃,即使复杂也是另一层次的复杂。有序的复杂性是可以控制和把握的,虽然编程也很辛苦。但这时经过几天几夜熬下来,终于可以欣慰地说:我们的程序可以稳定运行了!

时间: 2024-11-27 23:01:33

[转]李战大师-悟透delphi-第三章 多线程的相关文章

[转]李战大师-悟透delphi 第十一章 面向对象数据库基础

第十一章  面向对象数据库基础 第二节 数据对象的标识我们在关系数据库的设计和开发中,可能经常需要一些唯一的编号或标识,用来作为关键字,以区别每一个不同的人,每一张不同的单据,每一次不同的信息登记,等等.并且,我们也一直采用这些编号和标识,作为关系的连接字段.但是,要保证编号或标识是完全唯一的,却是一个不大不小的难题.下面我们将详细讨论这一问题,并希望能从另一个高度来理解这一问题.不过,我们首先来看看问题是怎样由来的.现在,给大家讲一个故事. 从前,在北京降生了一个漂亮的小女孩.接生的李阿姨说,

[转]李战大师-悟透delphi 第十章 操作界面与操作逻辑

第十章          操作界面与操作逻辑 我们在前面的曾经讨论过,用户界面与商业逻辑分离的好处.这样的分离可以让软件体系结构更加合理,结构易于理解,从而增强软件的灵活性和可维护性.正如我谈到过,我们讨论的目的是为了寻找将软件结构从混沌归于有序的实用方法,这是编写本书的主要目的之一.有序的东西易于理解,易于理解就便于掌握,掌握之后你将会发现其背后的哲理是那样的简单,从而升华到更高的境界去感受良好软件结构的协调美.本章的话题将重点讨论如何将客户端的程序有序化,希望我们的讨论对所有的软件同行和朋友

[转]李战大师-悟透delphi-第一章 delphi的原子世界

yjmyzz:李战大师的成名,并不是因为08年发表于园子里的那篇"悟透javascript",而是多年前的这篇处女作"悟透delphi",原出处已经找不到了,近日重温delphi研究如何开发原生win32中的activex控件时,无意又找到了这篇文章,想当年这篇文章在delphi编程群体中那是何等轰动,转载于此,以示纪念.(delphi的出现,秒杀了vb/pb,vs的出现又秒杀了delphi,但是windows就其发展来看,不管如何发展,至少在今后相当长的时候间,也

[转]李战大师-悟透delphi-第四章 接口

第四章          接口 前不久,有位搞软件的朋友给我出了个谜语.谜面是"相亲",让我猜一软件术语.我大约想了一分钟,猜出谜底是"面向对象".我觉得挺有趣,灵机一动想了一个谜语回敬他.谜面是"吻",也让他猜一软件术语.一分钟之后,他风趣地说:"你在面向你美丽的对象时,当然忍不住要和她接口!".我们同时哈哈大笑起来.谈笑间,似乎我们与自己的程序之间的感情又深了一层.对我们来说,软件就是生活. 第一节 接口的概念"

[转]李战大师-悟透delphi-第五章 包

第五章          包 我们在日常生活中会用到各式各样的包,钱包.公文包.背包.书包--,包里面都装有经常要用的东西.一旦你那天要出门远行,带上该带的包就可以了.不用再考虑包里面具体的东西,尽管包里有的东西用得着而有的东西用不着.有了这些包,你就可以一身轻松地旅游.当然,打包打得不好或者包太多也会成为累赘.同样,在DELPHI中,你可以将常用的程序和数据放到包里,让这些包伴随你的程序发布和运行.这时候,程序可以变得很短小而轻盈,轻松一身.特别是在包可以被多个应用程序模块共同使用的情况下,可

李战:悟透JavaScript

多年前,曾经看过李战大师的"悟透delphi-delphi的原子世界",一直对大师特有的文笔风格记忆犹新,今天无意又看到了大师的"李战:悟透JavaScript",转贴于此,与众分享!   引子    编程世界里只存在两种基本元素,一个是数据,一个是代码.编程世界就是在数据和代码千丝万缕的纠缠中呈现出无限的生机和活力.     数据天生就是文静的,总想保持自己固有的本色:而代码却天生活泼,总想改变这个世界.    你看,数据代码间的关系与物质能量间的关系有着惊人的相

悟透JavaScript出版啦

前天李战老师发消息来说我们合作的<悟透JavaScript>终于出版了 下午兴冲冲拿到样书,第一次看着自己的涂鸦变成印刷品,自然是十分激动 没错,就是涂鸦! 当然,这其实是一本讲JavaScript的好书 至于为什么会有这么神奇的一本程序书籍,却有一段小故事 当时出版社决定打破技术书籍沉闷的惯例 让这本书读起来更加有趣一点 可惜找了好几个插画er都不尽如人意 程序员和艺术家也许很难沟通 结果刚好碰上我这个懂一点编程又懂一点漫画的打杂小妹 于是李老师决定慷慨的给我这个机会为本书配置插图 从此后的

悟透三点思路 让你不再为软文投稿而苦恼

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 笔者在初初接触seo的时候就听说过"软文"这一名词,并且得知软文是做seo的一大利器,它既能带来大量高质量的自发外链,也能在文章在起到品牌推广的作用.但是写软文不容易,写高质量的软文更是困难,笔者也是在a5.chinaz投稿多次,却大多数已失败告终.笔者也灰心过,迷茫过,但是想到软文策划那个个带来的巨大价值却让我一直坚持,

雷军悟透网游3个阶级 自称找到做游戏秘笈

雷军悟透网游三个阶级层次:无产阶级抗争型游戏,典型的代表就是<征途>.中产阶级互助型的游戏,典型的代表是<魔兽争霸>.和谐社会街头大妈型游戏,典型的代表就是<梦幻西游>.以前和金山雷军聊天,他一直不理解为什么网易的<梦幻西游>能够创造131万人同时在线人数的世界纪录,而且历时5年不衰.既没有特别漂亮的画面,也没有炫目的特技:既没有紧张的情节,也没有特别丰富的道具,就如此让玩家乐此不疲.前两天,又和雷军聊天,他说终于找到中国游戏的发展方向了.而且还习得一个做游