第五节:多个线程同时执行相同的任务
 
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. python全栈开发-hashlib模块(数据加密)、suprocess模块、xml模块

    一.hashlib模块 1.什么叫hash:hash是一种算法(3.x里代替了md5模块和sha模块,主要提供 SHA1, SHA224, SHA256, SHA384, SHA512 ,MD5 算法 ...

  2. Linux中的重启命令

    1.系统重启: shutdowm    该命令安全地将系统关机. 有些用户会使用直接断掉电源的方式来关闭linux,这是十分危险的.因为linux与windows不同,其后台运行着许多进程,所以强制关 ...

  3. vi和vim编辑器

    VI vi是一种计算机文本编辑器,由美国计算机科学家比尔·乔伊(Bill Joy)完成编写,并于1976年以BSD协议授权发布. VIM Vim是从vi发展出来的一个文本编辑器.其代码补完.编译及错误 ...

  4. pandas笔记

    axis = 1表示按列的方向遍历 axis = 0表示按行的方向遍历 Usually axis=0 is said to be "column-wise" (and axis=1 ...

  5. Python面向对象——重写与Super

    1本文的意义 如果给已经存在的类添加新的行为,采用继承方案 如果改变已经存在类的行为,采用重写方案 2图解继承.重写与Super 注:上面代码层层关联.super()可以用到任何方法里进行调用,本文只 ...

  6. hash详解

    首先介绍一下hash? 事实上是一种叫做蛤丝的病毒 hash的做法: 首先设一个进制数base,并设一个模数mod 而哈希其实就是把一个数转化为一个值,这个值是base进制的,储存在哈希表中,注意一下 ...

  7. Python的基础学习(第二周)

    模块初始 sys模块 import sys sys.path #打印环境变量 sys.argv#打印该文件路径 #注意:该文件名字不能跟导入模块名字相同 os模块 import os cmd_res ...

  8. Mlecms Getshell

    参考来源:https://bbs.ichunqiu.com/thread-13703-1-1.html 位于:/inc/include/globals.php 第24-28行.有个任意变量覆盖. fo ...

  9. Css实现checkbox及radio样式自定义

    前言 checkbox和radio样式自定义在网页中是很常见的, 比如在进行表单输入时性别的选择,用户注册时选择已阅读用户协议.随着用户对产品体验要求越来越高,我们都会对checkbox和radio重 ...

  10. [SDOI2016]硬币游戏

    题目描述 Alice 和 Bob 现在在玩的游戏,主角是依次编号为 1 到 n 的 n 枚硬币.每一枚硬币都有两面,我们分别称之为正面和反面.一开始的时候,有些硬币是正面向上的,有些是反面朝上的.Al ...