第五节:多个线程同时执行相同的任务
 
1.锁
 
设,有一个房间 X ,X为全局变量,它有两个函数  X.Lock 与 X.UnLock;
有如下代码:
 
X.Lock;  
   访问资源 P;
X.Unlock;
 
现在有A,B两个线程时空都要执行此段代码。
当线程A执行了 X.Lock 之后,在没有执行完  X.Unlock 之前,第二个线程B此时也来执行 X.Lock ,
线程B就会阻塞在 X.Lock 这句代码上。我们可以认为,此时,线程A进入房间,其它线程不准再进入房间。
只能在外面等,直到线程A执行完 X.Unlock 后,线程A退出了房间,此时线程B才可以进入。
线程B进入了房间后,其它线程此时同样不准再进入。
 
即:多个线程用本段代码“访问资源P”的操作是排队执行的。
 
2.  TMonitor
 
在 delphi XE2 及以后的版本中,提供了一个方便的锁功能。TMonitor。
它是一个Record, TMonitor.Enter(X); 与 TMoniter.Exit(X); 等效于上面 lock 与 unlock;
X 可以是任何一个 TObject 实例。
 
本例源码下载(delphi XE8版本):FooMuliThread.zip
 
unit uCountThread; 
interface 
uses
  uFooThread; 
type
  TCountThread = class;
  TOnGetNum = function(Sender: TCountThread): boolean of object//获取 Num 事件。
  TOnCounted = procedure(Sender: TCountThread) of object;
  TCountThread = class(TFooThread)
  private
    procedure Count;
    procedure DoOnCounted;
    function DoOnGetNum: boolean;
  public
    procedure StartThread; override;
  public
    Num: integer;
    Total: integer;
    OnCounted: TOnCounted;
    OnGetNum: TOnGetNum;
    ThreadName: string;
  end;
 
implementation
 
{ TCountThread }
 
procedure TCountThread.Count;
var
  i: integer;
begin
 
  // 注意多线程不适合打断点调试。
  // 因为一旦在 IDE 中断后,状态全乱了。
  // 可以写 Log 或用脑袋想,哈哈。
 
  if DoOnGetNum then // 获取参数 Num
  begin
    Total := 0;
    if Num > 0 then
      for i := 1 to Num do
      begin
        Total := Total + i;
        sleep(5); //嫌慢就删掉此句。
      end;
    DoOnCounted; // 引发 OnCounted 事件,告知调用者。
    ExecProcInThread(Count); // 上节说到在线程时空里执行本句。
  end;
 
end;
 
procedure TCountThread.DoOnCounted;
begin
  if Assigned(OnCounted) then
    OnCounted(self);
end;
 
function TCountThread.DoOnGetNum: boolean;
begin
  result := false;
  if Assigned(OnGetNum) then
    result := OnGetNum(self);
end;
 
procedure TCountThread.StartThread;
begin
  inherited;
  ExecProcInThread(Count); // 把 Count 过程塞到线程中运行。
end;
 
end.
 
unit uFrmMain;
interface
uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, uCountThread;
 
type
  TFrmMain = class(TForm)
    memMsg: TMemo;
    edtNum: TEdit;
    btnWork: TButton;
    lblInfo: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure btnWorkClick(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  private
    { Private declarations }
    FCo1, FCo2, FCo3: TCountThread; // 定义了3个线程实例
    // 以后章节将讲解采用 List 容器来装线程实例。
    FBuff: TStringList;
    FBuffIndex: integer;
    FBuffMaxIndex: integer;
    FWorkedCount: integer;
    procedure DispMsg(AMsg: string);
    procedure OnThreadMsg(AMsg: string);
 
    function OnGetNum(Sender: TCountThread): Boolean;
    procedure OnCounted(Sender: TCountThread);
 
    procedure LockBuffer;
    procedure UnlockBuffer;
 
    procedure LockCount;
    procedure UnlockCount;
 
  public
    { Public declarations }
  end;
 
var
  FrmMain: TFrmMain;
 
implementation
 
{$R *.dfm}
{ TFrmMain }
 
procedure TFrmMain.btnWorkClick(Sender: TObject);
var
  s: string;
begin
 
  btnWork.Enabled := false;
  FWorkedCount := 0;
  FBuffIndex := 0;
  FBuffMaxIndex := FBuff.Count - 1;
 
  s := '共' + IntToStr(FBuffMaxIndex + 1) + '个任务,已完成:' + IntToStr(FWorkedCount);
  lblInfo.Caption := s;
 
  FCo1.StartThread;
  FCo2.StartThread;
  FCo3.StartThread;
 
end;
 
procedure TFrmMain.DispMsg(AMsg: string);
begin
  memMsg.Lines.Add(AMsg);
end;
 
procedure TFrmMain.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  // 防止计算期间退出
  LockCount; // 请思考,这里为什么要用 LockCount;
  CanClose := btnWork.Enabled;
  if not btnWork.Enabled then
    DispMsg('正在计算,不准退出!');
  UnlockCount;
end;
 
procedure TFrmMain.FormCreate(Sender: TObject);
begin
 
  FCo1 := TCountThread.Create(false);
  FCo1.OnStatusMsg := self.OnThreadMsg;
  FCo1.OnGetNum := self.OnGetNum;
  FCo1.OnCounted := self.OnCounted;
  FCo1.ThreadName := '线程1';
 
  FCo2 := TCountThread.Create(false);
  FCo2.OnStatusMsg := self.OnThreadMsg;
  FCo2.OnGetNum := self.OnGetNum;
  FCo2.OnCounted := self.OnCounted;
  FCo2.ThreadName := '线程2';
 
  FCo3 := TCountThread.Create(false);
  FCo3.OnStatusMsg := self.OnThreadMsg;
  FCo3.OnGetNum := self.OnGetNum;
  FCo3.OnCounted := self.OnCounted;
  FCo3.ThreadName := '线程3';
 
  FBuff := TStringList.Create;
 
  // 构造一组数据用来测试
 
  FBuff.Add('100');
  FBuff.Add('136');
  FBuff.Add('306');
  FBuff.Add('156');
  FBuff.Add('152');
  FBuff.Add('106');
  FBuff.Add('306');
  FBuff.Add('156');
  FBuff.Add('655');
  FBuff.Add('53');
  FBuff.Add('99');
  FBuff.Add('157');
 
end;
 
procedure TFrmMain.FormDestroy(Sender: TObject);
begin
  FCo1.Free;
  FCo2.Free;
  FCo3.Free;
end;
 
procedure TFrmMain.LockBuffer;
begin
  System.TMonitor.Enter(FBuff);
  // System 是单元名。因为 TMonitor 在 Forms 中也有一个相同的名字。
  // 同名的类与函数,就要在前面加单元名称以示区别。
end;
 
procedure TFrmMain.LockCount;
begin
  // 任意一个 TObject 就行,所以我用了 btnWork
  System.TMonitor.Enter(btnWork);
end;
 
procedure TFrmMain.OnCounted(Sender: TCountThread);
var
  s: string;
begin
 
  LockCount; // 此处亦可以用 LockBuffer
  // 但是,锁不同的对象,宜用不同的锁。
  // 每把锁的功能要单一,锁的粒度要最小化。才能提高效率。
 
  s := Sender.ThreadName + ':' + IntToStr(Sender.Num) + '累加和为:';
  s := s + IntToStr(Sender.Total);
  OnThreadMsg(s);
 
  inc(FWorkedCount);
 
  s := '共' + IntToStr(FBuffMaxIndex + 1) + '个任务,已完成:' + IntToStr(FWorkedCount);
 
  TThread.Synchronize(nil,
    procedure
    begin
      lblInfo.Caption := s;
    end);
 
  if FWorkedCount >= FBuffMaxIndex + 1 then
  begin
    TThread.Synchronize(nil,
      procedure
      begin
        DispMsg('已计算完成');
        btnWork.Enabled := true// 恢复按钮状态。
      end);
  end;
 
  UnlockCount;
 
end;
 
function TFrmMain.OnGetNum(Sender: TCountThread): Boolean;
begin
  LockBuffer; // 将多个线程访问 FBuff 排队。
  try
    if FBuffIndex > FBuffMaxIndex then
    begin
      result := false;
    end
    else
    begin
      Sender.Num := StrToInt(FBuff[FBuffIndex]);
      result := true;
      inc(FBuffIndex);
    end;
  finally
    UnlockBuffer;
  end;
end;
 
procedure TFrmMain.OnThreadMsg(AMsg: string);
begin
  TThread.Synchronize(nil,
    procedure
    begin
      DispMsg(AMsg);
    end);
end;
 
procedure TFrmMain.UnlockBuffer;
begin
  System.TMonitor.Exit(FBuff);
end;
 
procedure TFrmMain.UnlockCount;
begin
  System.TMonitor.Exit(btnWork);
end;
 
end.
 
下一节,我们将学习 List 与泛型。为以后设计其它的更高级与灵活的线程做准备。
 
 
 


delphi 线程教学第五节:多个线程同时执行相同的任务的更多相关文章

  1. delphi 线程教学第六节:TList与泛型

    第六节: TList 与泛型   TList 是一个重要的容器,用途广泛,配合泛型,更是如虎添翼. 我们先来改进一下带泛型的 TList 基类,以便以后使用. 本例源码下载(delphi XE8版本) ...

  2. delphi 线程教学第四节:多线程类的改进

    第四节:多线程类的改进   1.需要改进的地方   a) 让线程类结束时不自动释放,以便符合 delphi 的用法.即 FreeOnTerminate:=false; b) 改造 Create 的参数 ...

  3. delphi 线程教学第七节:在多个线程时空中,把各自的代码塞到一个指定的线程时空运行

    第七节:在多个线程时空中,把各自的代码塞到一个指定的线程时空运行     以 Ado 为例,常见的方法是拖一个 AdoConnection 在窗口上(或 DataModule 中), 再配合 AdoQ ...

  4. delphi 线程教学第二节:在线程时空中操作界面(UI)

    第二节:在线程时空中操作界面(UI)   1.为什么要用 TThread ?   TThread 基于操作系统的线程函数封装,隐藏了诸多繁琐的细节. 适合于大部分情况多线程任务的实现.这个理由足够了吧 ...

  5. delphi 线程教学第一节:初识多线程

    第一节:初识多线程   1.为什么要学习多线程编程?   多线程(多个线程同时运行)编程,亦可称之为异步编程. 有了多线程,主界面才不会因为耗时代码而造成“假死“状态. 有了多线程,才能使多个任务同时 ...

  6. delphi 线程教学第三节:设计一个有生命力的工作线程

    第三节:设计一个有生命力的工作线程   创建一个线程,用完即扔.相信很多初学者都曾这样使用过. 频繁创建释放线程,会浪费大量资源的,不科学.   1.如何让多线程能多次被复用?   关键是不让代码退出 ...

  7. {Python之线程} 一 背景知识 二 线程与进程的关系 三 线程的特点 四 线程的实际应用场景 五 内存中的线程 六 用户级线程和内核级线程(了解) 七 python与线程 八 Threading模块 九 锁 十 信号量 十一 事件Event 十二 条件Condition(了解) 十三 定时器

    Python之线程 线程 本节目录 一 背景知识 二 线程与进程的关系 三 线程的特点 四 线程的实际应用场景 五 内存中的线程 六 用户级线程和内核级线程(了解) 七 python与线程 八 Thr ...

  8. 第三百七十五节,Django+Xadmin打造上线标准的在线教育平台—创建课程机构app,在models.py文件生成3张表,城市表、课程机构表、讲师表

    第三百七十五节,Django+Xadmin打造上线标准的在线教育平台—创建课程机构app,在models.py文件生成3张表,城市表.课程机构表.讲师表 创建名称为app_organization的课 ...

  9. 并发编程概述 委托(delegate) 事件(event) .net core 2.0 event bus 一个简单的基于内存事件总线实现 .net core 基于NPOI 的excel导出类,支持自定义导出哪些字段 基于Ace Admin 的菜单栏实现 第五节:SignalR大杂烩(与MVC融合、全局的几个配置、跨域的应用、C/S程序充当Client和Server)

    并发编程概述   前言 说实话,在我软件开发的头两年几乎不考虑并发编程,请求与响应把业务逻辑尽快完成一个星期的任务能两天完成绝不拖三天(剩下时间各种浪),根本不会考虑性能问题(能接受范围内).但随着工 ...

随机推荐

  1. eclipse使用git及github学习笔记

    项目托管 1.首先需要在github上建立一个远端仓库  点击Create repository后,会在github上建立相应的git仓库,并会出现如下界面: 复制https或者ssh的仓库地址,远端 ...

  2. github生成SSH公钥

    ssh-keygen -t rsa -C "your_email@youremail.com" 然后输入github上的密码 Enter passphrase (empty for ...

  3. netcore webapi帮助文档设置

    如何建 .netcore webapi 项目这个就不说了,这个都没有没必要看下去. 我这里是.netcore 2.0,虽然没测过1.0的,但想来差不多. 1.Nuget Packages安装,使用程序 ...

  4. 《阿里巴巴 Java 开发手册》读书笔记

    偶然看到阿里巴巴居然出书了???趁着满减活动(节约节约....)我赶紧买来准备看看,刚拿到的时候掂量了好多下,总觉得商家给我少发了一本书,结果打开才知道..原来这本书这么小.... 编码规范的重要性 ...

  5. [LeetCode] Maximum Product of Three Numbers 三个数字的最大乘积

    Given an integer array, find three numbers whose product is maximum and output the maximum product. ...

  6. 【Swift】swift定义全局变量

    swift定义全局变量非常简单哈,只要在类class上面直接定义,就是全局变量了 当需要在类里面定义一个类函数访问的变量的时候,直接在var或者let 前面加一个 static

  7. hadoop一键安装伪分布式

    hadoop伪分布式和hive在openSUSE中的安装 在git上的路径为:https://github.com/huabingood/hadoop--------/tree/master 各个文件 ...

  8. [CQOI2013]棋盘游戏

    Description 一个n*n(n>=2)棋盘上有黑白棋子各一枚.游戏者A和B轮流移动棋子,A先走. A的移动规则:只能移动白棋子.可以往上下左右四个方向之一移动一格. B的移动规则:只能移 ...

  9. clique

    [题目描述]数轴上有 n 个点,第 i 个点的坐标为 xi,权值为 wi.两个点 i,j 之间存在一条边当且仅当 abs(xi-xj)>=wi+wj.你需要求出这张图的最大团的点数.(团就是两两 ...

  10. bzoj 4919: [Lydsy六月月赛]大根堆

    Description 给定一棵n个节点的有根树,编号依次为1到n,其中1号点为根节点.每个点有一个权值v_i. 你需要将这棵树转化成一个大根堆.确切地说,你需要选择尽可能多的节点,满足大根堆的性质: ...