第五节:多个线程同时执行相同的任务
 
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. NOPI实现导入导出泛型List,支持自定义列

    概述 业务上需要自定义列的Excel的导入导出,在网上看了好多资料,很多都是有Bug而且都是支持Excel和DataTable的转换,所以自己总结了一下,应用.NET平台上的NPOI封装了支持自定义列 ...

  2. [机器学习实战]K-近邻算法

    1. K-近邻算法概述(k-Nearest Neighbor,KNN) K-近邻算法采用测量不同的特征值之间的距离方法进行分类.该方法的思路是:如果一个样本在特征空间中的k个最相似(即特征空间中最邻近 ...

  3. CentOS7从U盘中拷贝文件

    1. 要想从U盘中拷贝文件,必须要将U盘挂载到一个目录中,所以必须新建一个目录,一般建在/mnt下.我们执行:mkdir /mnt/usb来新建一个目录. 2. 查看U盘是否已经被识别.执行:df - ...

  4. Spark:导入数据到oracle

    方案一: //overwrite JdbcDialect fitting for Oracle val OracleDialect = new JdbcDialect { override def c ...

  5. scrapy爬取中关村在线手机频道

    # -*- coding: utf-8 -*- import scrapy from pyquery import PyQuery as pq from zolphone.items import Z ...

  6. pyspider爬取TripAdvisor

    #!/usr/bin/env python # -*- encoding: utf-8 -*- # Created on 2017-06-11 10:10:53 # Project: london f ...

  7. DbContext(String)+SqlQuery一起使用

    DbContext(String) 可以将给定字符串用作将连接到的数据库的名称或连接字符串来构造一个新的上下文实例. Database.SqlQuery 方法 (Type, String, Objec ...

  8. ionic新入坑-环境搭建+新建项目+打开低版本项目处理

    是的.我又双叒叕入新坑了.想我大学的时候web-app刚火起来.还帮忙做了我们学校医务室系统的web-app页面部分呢.时间太紧最后也没出个完整的版本.那时候只是用H5简单做了web部分.是想着用ph ...

  9. CentOS安装node.js-8.11.1+替换淘宝NPM镜像

    注:以下所有操作均在CentOS 6.8 x86_64位系统下完成. #准备工作# 由于node.js-8.11.1在源码编译安装的时候需要gcc 4.9.4或clang++ 3.4.2以上版本的支持 ...

  10. [LeetCode] Next Greater Element I 下一个较大的元素之一

    You are given two arrays (without duplicates) nums1 and nums2 where nums1’s elements are subset of n ...