第五节:多个线程同时执行相同的任务
 
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. Java-NIO(三):直接缓冲区与非直接缓冲区

    直接缓冲区与非直接缓冲区的概念: 1)非直接缓冲区:通过 static ByteBuffer allocate(int capacity) 创建的缓冲区,在JVM中内存中创建,在每次调用基础操作系统的 ...

  2. Effective Java 第三版——38. 使用接口模拟可扩展的枚举

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  3. MySQL 表空间传输

    聊到MySQL数据迁移的话题,表空间传输时一个很实用的方法. 在MySQL 5.6 Oracle引入了一个可移动表空间的特征(复制的表空间到另一个服务器)和Percona Server采用部分备份,这 ...

  4. 深入理解Redux

    前面的话 Redux是Flux思想的另一种实现方式.Flux是和React同时面世的.React用来替代jQuery,Flux用来替换Backbone.js等MVC框架.在MVC的世界里,React相 ...

  5. [LeetCode] Max Chunks To Make Sorted 可排序的最大块数

    Given an array arr that is a permutation of [0, 1, ..., arr.length - 1], we split the array into som ...

  6. [LeetCode] Parse Lisp Expression 解析Lisp表达式

    You are given a string expression representing a Lisp-like expression to return the integer value of ...

  7. 再深刻理解下web3.js中estimateGas如何计算智能合约消耗的gas量

    我们可使用web3.js框架的estimateGas函数获得一个以太坊智能合约的Gas估计值 ,通过执行一个消息调用或交易,该消息调用或交易直接在节点的VM中执行,并未在区块链中确认,函数会返回估算使 ...

  8. 简陋的个人Vim使用命令

    最近把Visual Studio 的编辑器改成了 Vim,感觉像发现了新世界,记录记录一些提高效率的Vim命令. 插入命令 i 在当前位置前插入 I 在当前行首插入 a 在当前位置后插入 A 在当前行 ...

  9. 【NOIP2009】Hankson 的趣味题

    题目描述 Hanks 博士是 BT (Bio-Tech,生物技术) 领域的知名专家,他的儿子名叫 Hankson.现在,刚刚放学回家的 Hankson 正在思考一个有趣的问题. 今天在课堂上,老师讲解 ...

  10. Codeforces Round #460 E. Congruence Equation

    Description 题面 \(n*a^n≡b (\mod P),1<=n<=x\) Solution 令 \(n=(P-1)*i+j\) \([(P-1)*i+j]*a^{[(P-1) ...