第五节:多个线程同时执行相同的任务
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 与泛型。为以后设计其它的更高级与灵活的线程做准备。