前言
由于项目的关系,所以和线程的接触颇多,常常遇到问题,常常看
TThread
的代码,又常常想一些办法来解决遇到的问题,所以就有了这篇文章。
正文
我们常有工作线程和主线程之分,工作线程负责作一些后台操作,比如接收邮件;主线程负责界面上的一些显示。工作线程的好处在某些时候是不言而喻的,你的主界面可以响应任何操作,而背后的线程却在默默地工作。
VCL
中,工作线程执行在
Execute
方法中,你必须从
TThread
继承一个类并覆盖
Execute
方法,在这个方法中,所有代码都是在另一个线程中执行的,除此之外,你的线程类的其他方法都在主线程执行,包括构造方法,析构方法,
Resume
等,很多人常常忽略了这一点。
最简单的一个线程类如下:
TMyThread = class(TThread)
protected
procedure Execute; override;
end;
在
Execute
中的代码,有一个技术要点,如果你的代码执行时间很短,像这样,
Sleep(1000)
,那没有关系;如果是这样
Sleep(10000)
,
10
秒,那么你就不能直接这样写了,须把这
10
秒拆分成
10
个
1
秒,然后判断
Terminated
属性,像下面这样:
procedure TMyThread.Execute;
var
i: Integer;
begin
for i := 0 to 9 do
if not Terminated then
Sleep(1000)
else
Break;
end;
这样写有什么好处呢,想想你要关闭程序,在关闭的时候调用
MyThread.Free
,这个时候线程并没有马上结束,它调用
WaitFor
,等待
Execute
执行完后才能释放。你的程序就必须等
10
秒以后才能关闭,受得了吗。如果像上面那样写,在程序关闭时,调用
Free
之后,它顶多再等一秒就会关闭。为什么?答案得去线程类的
Destroy
中找,它会先调用
Terminate
方法,在这个方法里面它把
Terminated
设为
True
(仅此而已,很多人以为是结束线程,其实不是)。请记住这一切是在主线程中操作的,所以和
Execute
是并行执行的。既然
Terminated
属性已为
Ture
,那么在
Execute
中判断之后,当然就
Break
了,
Execute
执行完毕,线程类也正常释放。
或者有人说,
TThread
可以设
FreeOnTerminate
属性为
True
,线程类就能自动释放。除非你的线程执行的任务很简单,不然,还是不要去理会这个属性,一切由你来操作,才能使线程更灵活强大。
接下来的问题是如何使工作线程和主线程很好的通信,很多时候主线程必须得到工作线程的通知,才能做出响应。比如接收邮件,工作线程向服务器收取邮件,收取完毕之后,它得通知主线程收到多少封邮件,主线程才能弹出一个窗口通知用户。
在
VCL
中,我们可以用两种方法,一种是向主线程中的窗体发送消息,另一种是使用异步事件。第一种方法其实没有第二种来得方便。想想线程类中的
OnTerminate
事件,这个事件由线程函数的堆栈引起,却在主线程执行。
事实上,真正的线程函数是这个:
function ThreadProc(Thread: TThread): Integer;
函数里面有
Thread.Execute
,这就是为什么
Execute
是在其他线程中执行,该方法执行之后,有如下语句:
Thread.DoTerminate;
而线程类的
DoTerminate
方法里面是
if Assigned(FOnTerminate) then Synchronize(CallOnTerminate);
显然
Synchronize
方法使得
CallOnTerminate
在主线程中执行,而
CallOnTerminate
里面的代码其实就是:
if Assigned(FOnTerminate) then FOnTerminate(Self);
只要
Execute
方法一执行完就发生
OnTerminate
事件。不过有一点是必须注意,
OnTerminate
事件发生后,线程类不一定会释放,只有在
FreeOnTerminate
为
True
之后,才会
Thread.Free
。看一下
ThreadProc
函数就知道。
依照
Onterminate
事件,我们可以设计自己的异步事件。
Synchronize
方法只能传进一个无参数的方法类型,但我们的事件经常是要带一些参数的,这个稍加思考就可以得到解决,即在线程类中保存参数,触发事件前先设置参数,再调用异步事件,参数复杂的可以用记录或者类来实现。
假设这样,上面的代码每睡一秒,线程即向外面引发一次事件,我们的类可以这样设计:
TSecondEvent =
procedure
(Second: Integer)
of
object
;
TMyThread = class (TThread)
private
FSecond: Integer;
FSecondEvent: TSecondEvent;
procedure CallSecondEvent;
protected
procedure Execute; override;
public
property SencondEvent: TSecondEvent read FSecondEvent
write FSecondEvent;
end ;
{ TMyThread }
procedure TMyThread.CallSecondEvent;
begin
if Assigned(FSecondEvent) then
FSecondEvent(FSecond);
end ;
procedure TMyThread.Execute;
var
i: Integer;
begin
for i := 0 to 9 do
if not Terminated then
begin
Sleep( 1000 );
FSecond := i;
Synchronize(CallSecondEvent);
end
else
Break;
end ;
TMyThread = class (TThread)
private
FSecond: Integer;
FSecondEvent: TSecondEvent;
procedure CallSecondEvent;
protected
procedure Execute; override;
public
property SencondEvent: TSecondEvent read FSecondEvent
write FSecondEvent;
end ;
{ TMyThread }
procedure TMyThread.CallSecondEvent;
begin
if Assigned(FSecondEvent) then
FSecondEvent(FSecond);
end ;
procedure TMyThread.Execute;
var
i: Integer;
begin
for i := 0 to 9 do
if not Terminated then
begin
Sleep( 1000 );
FSecond := i;
Synchronize(CallSecondEvent);
end
else
Break;
end ;
在主窗体中假设我们这样操作线程:
procedure
TForm1.Button1Click(Sender: TObject);
begin
MyThread := TMyThread.Create(true);
MyThread.OnTerminate := ThreadTerminate;
MyThread.SencondEvent := SecondEvent;
MyThread.Resume;
end ;
procedure TForm1.ThreadTerminate(Sender: TObject);
begin
ShowMessage( 'ok' );
end ;
procedure TForm1.SecondEvent(Second: Integer);
begin
Edit1.Text := IntToStr(Second);
end ;
begin
MyThread := TMyThread.Create(true);
MyThread.OnTerminate := ThreadTerminate;
MyThread.SencondEvent := SecondEvent;
MyThread.Resume;
end ;
procedure TForm1.ThreadTerminate(Sender: TObject);
begin
ShowMessage( 'ok' );
end ;
procedure TForm1.SecondEvent(Second: Integer);
begin
Edit1.Text := IntToStr(Second);
end ;
我们将每隔一秒就得到一次通知并在
Edit
中显示出来。
现在我们已经知道如何正确使用
Execute
方法,以及如何在主线程与工作线程之间通信了。但问题还没有结束,有一种情况出乎我的意料之外,即如果线程中有一些资源,
Execute
正在使用这些资源,而主线程要释放这个线程,这个线程在释放的过程中会释放掉资源。想想会不会有问题呢,两个线程,一个在使用资源,一个在释放资源,会出现什么情况呢,
用下面代码来说明:
type
TMyClass = class
private
FSecond: Integer;
public
procedure SleepOneSecond;
end ;
TMyThread = class (TThread)
private
FMyClass: TMyClass;
protected
procedure Execute; override;
public
constructor MyCreate(CreateSuspended: Boolean);
destructor Destroy; override;
end ;
implementation
{ TMyThread }
constructor TMyThread.MyCreate(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
FMyClass := TMyClass.Create;
end ;
destructor TMyThread.Destroy;
begin
FMyClass.Free;
FMyClass := nil ;
inherited ;
end ;
procedure TMyThread.Execute;
var
i: Integer;
begin
for i := 0 to 9 do
FMyClass.SleepOneSecond;
end ;
{ TMyClass }
procedure TMyClass.SleepOneSecond;
begin
FSecond := 0 ;
Sleep( 1000 );
end ;
end .
TMyClass = class
private
FSecond: Integer;
public
procedure SleepOneSecond;
end ;
TMyThread = class (TThread)
private
FMyClass: TMyClass;
protected
procedure Execute; override;
public
constructor MyCreate(CreateSuspended: Boolean);
destructor Destroy; override;
end ;
implementation
{ TMyThread }
constructor TMyThread.MyCreate(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
FMyClass := TMyClass.Create;
end ;
destructor TMyThread.Destroy;
begin
FMyClass.Free;
FMyClass := nil ;
inherited ;
end ;
procedure TMyThread.Execute;
var
i: Integer;
begin
for i := 0 to 9 do
FMyClass.SleepOneSecond;
end ;
{ TMyClass }
procedure TMyClass.SleepOneSecond;
begin
FSecond := 0 ;
Sleep( 1000 );
end ;
end .
用下面的代码来调用上面的类:
procedure
TForm1.Button1Click(Sender: TObject);
begin
MyThread := TMyThread.MyCreate(true);
MyThread.OnTerminate := ThreadTerminate;
MyThread.Resume;
end ;
procedure TForm1.Button2Click(Sender: TObject);
begin
MyThread.Free;
end ;
begin
MyThread := TMyThread.MyCreate(true);
MyThread.OnTerminate := ThreadTerminate;
MyThread.Resume;
end ;
procedure TForm1.Button2Click(Sender: TObject);
begin
MyThread.Free;
end ;
先点击
Button1
创建一个线程,再点击
Button2
释放该类,出现什么情况呢,违法访问,是的,
MyThread.Free
时,
MyClass
被释放掉了
FMyClass.Free;
FMyClass :=
nil
;
而此时
Execute
却还在执行,并且调用
MyClass
的方法,当然就出现违法访问。对于这种情况,有什么办法来防止呢,我想到一种方法,即在线程类中使用一个成员,假设为
FFinished
,在
Execute
方法中有如下的形式:
FFinished := False;
try
//... ...
finally
FFinished := True;
End;
接着在线程类的
Destroy
中有如下形式:
While not FFinished do
Sleep(100);
MyClass.Free;
这样便能保证
MyClass
能被正确释放。
线程是一种很有用的技术。但使用不当,常使人头痛。在
CSDN
论坛上看到一些人问,我的窗口在线程中调用为什么出错,主线程怎么向其他线程发送消息等等,其实,我们在抱怨线程难用时,也要想想我们使用的方法对不对,只要遵循一些正确的使用规则,线程其实很简单。
后记
上面有一处代码有些奇怪:
FMyClass.Free; FMyClass :=
nil
;
如果你只写
FMyClass.Free
,线程类还不会出现异常,即调用
FMyClass.SleepOneSecond
不会出错。我在主线程中试了下面的代码
MyClass := TMyClass.Create;
MyClass.SleepOneSecond;
MyClass.Free;
MyClass.SleepOneSecond;
同样也不会出错,但关闭程序时就出错了,如果是这样:
MyClass := TMyClass.Create;
MyClass.SleepOneSecond;
MyClass.Free;
MyThread := TMyThread.MyCreate(true);
MyThread.OnTerminate := ThreadTerminate;
MyThread.Resume;
MyClass.SleepOneSecond;
马上就出错。所以这个和线程类无线,应该是
Delphi
对于堆栈空间的释放规则,我想
MyClass.Free
之后,该对象在堆栈上空间还是保留着,只是允许其他资源使用这个空间,所以接着调用下面这一句
MyClass.SleepOneSecond
就不会出错,当程序退出时可能对堆栈作一些清理导致出错。而如果
MyClass.Free
之后即创建
MyThread
,大概
MyClass
的空间已经被
MyThread
使用,所以再调用
MyClass.SleepOneSecond
就出错了。
线程杂谈2 - [原创] <script language=JavaScript> document.title="线程杂谈2 - [原创] - "+document.title </script>
作者
:linzhenqun(
风
)
时间
:2006-2-6
-----------------------------------------------------------------------------------------------------
前言
上次写了一篇关于线程的文章,其中有介绍当工作线程有资源存在时,主线程如何释放工作线程的方法,后来仔细读了《
Win32
多线程程序设计》,对于线程的机制有了更进一步的理解,才知道那并非最佳的方法,这里将给出一个更好的方法,这也是我写这篇文章的最大原因。另外,对于同步机制也将作一些探讨。如果想全面了解多线程的,推荐看候捷翻译的《
Win32
多线程程序设计》。
干净地终止一个线程
回到上一篇的问题描述:
如果线程中有一些资源,
Execute
正在使用这些资源,而主线程要释放这个线程,这个线程在释放的过程中会释放掉资源。想想会不会有问题呢,两个线程,一个在使用资源,一个在释放资源,会出现什么情况呢。
具体的情况请看“线程杂谈”那篇文章。总之是出现了违法访问,而我给出的解决方法是使用一个
FFinished
成员,最后释放的时候循环判断
FFinished
,这样可以保证
Execute
执行完毕之前,线程的成员类不会被释放。利用线程类的
Terminated
属性和
FFinished
成员
,
便可在主线程干净结束工作线程。
但这并非最好的解决方法,循环判断
FFinished
成员即如下:
While not FFinished do
Sleep(100);
这并不能即时释放线程,最慢的情况还要等上
0.1
秒才能释放。而如果我们使用下面这种形式:
While not FFinished do
;
则是一种典型的
Busy loop
,主线程不停的使用
While
循环,非常浪费
CPU
的资源,这样的效率比直接等待慢一倍以上。
那么有没有一种方法,让主线程通知工作线程可以结束了,然后主线程等待工作线程结束,工作线程结束后才释放资源呢?让主线程通知工作线程结束可以用线程类的
Terminated
属性,这一点在上一篇文章已有介绍。而主线程等待
Execute
结束,事实上线程类的
Destroy
方法已经有这样的机制:
Terminate;
...
WaitFor;
其中的
WaitFor
方法一直等待直到
Execute
方法执行完毕(准确的说应该是
ThreadProc
函数执行完毕)。这些代码在一般的线程类中已经够用了,但有些线程类有自己的类成员,并会覆盖
Destroy
方法,在其中释放类成员,既然这样,我们可以把上面的代码包成一个方法,并在自己的
Destroy
中首先调用该方法。这样即可即时干净地释放线程类,比使用循环更好。
下面给出例子:
unit
Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
type
TMyClass = class
private
FTest: Integer;
public
procedure test;
end;
TMyThread = class(TThread)
private
FMyClass: TMyClass;
protected
(* 等待 Execute 方法执行完毕 *)
procedure WaitExecute;
procedure Execute; override;
public
constructor Create(CreateSuspended: Boolean);
destructor Destroy; override;
end;
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
procedure OnTerminate(Sender: TObject);
public
{ Public declarations }
MyThread: TMyThread;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TMyThread }
constructor TMyThread.Create(CreateSuspended: Boolean);
begin
inherited;
FMyClass := TMyClass.Create;
end ;
destructor TMyThread.Destroy;
begin
WaitExecute;
FMyClass.Free;
FMyClass := nil;
inherited;
end ;
procedure TMyThread.Execute;
begin
while not Terminated do
FMyClass.test;
end ;
procedure TMyThread.WaitExecute;
begin
Terminate;
WaitFor;
end ;
{ TMyClass }
procedure TMyClass.test;
begin
// 用 Sleep 模拟业务操作
Sleep( 100 );
FTest := 0 ;
end ;
procedure TForm1.Button1Click(Sender: TObject);
begin
MyThread := TMyThread.Create(False);
MyThread.OnTerminate := OnTerminate;
end ;
procedure TForm1.Button2Click(Sender: TObject);
begin
MyThread.Free;
end ;
procedure TForm1.OnTerminate(Sender: TObject);
begin
ShowMessage( 'OK' );
end ;
end .
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
type
TMyClass = class
private
FTest: Integer;
public
procedure test;
end;
TMyThread = class(TThread)
private
FMyClass: TMyClass;
protected
(* 等待 Execute 方法执行完毕 *)
procedure WaitExecute;
procedure Execute; override;
public
constructor Create(CreateSuspended: Boolean);
destructor Destroy; override;
end;
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
procedure OnTerminate(Sender: TObject);
public
{ Public declarations }
MyThread: TMyThread;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TMyThread }
constructor TMyThread.Create(CreateSuspended: Boolean);
begin
inherited;
FMyClass := TMyClass.Create;
end ;
destructor TMyThread.Destroy;
begin
WaitExecute;
FMyClass.Free;
FMyClass := nil;
inherited;
end ;
procedure TMyThread.Execute;
begin
while not Terminated do
FMyClass.test;
end ;
procedure TMyThread.WaitExecute;
begin
Terminate;
WaitFor;
end ;
{ TMyClass }
procedure TMyClass.test;
begin
// 用 Sleep 模拟业务操作
Sleep( 100 );
FTest := 0 ;
end ;
procedure TForm1.Button1Click(Sender: TObject);
begin
MyThread := TMyThread.Create(False);
MyThread.OnTerminate := OnTerminate;
end ;
procedure TForm1.Button2Click(Sender: TObject);
begin
MyThread.Free;
end ;
procedure TForm1.OnTerminate(Sender: TObject);
begin
ShowMessage( 'OK' );
end ;
end .
先点击
Button1
,然后点击
Button2
,线程类可以被正确地释放,
试着把
Destroy
方法中的
WaitExecute
方法注释掉再点击按钮,接着关闭程序看看,这时就出现违法访问了。
同步机制
当有多个线程同时读写一个资源时,使用同步机制可以保护数据的完整性,也可以防止数据被损坏。线程同步有很多种方式,但最常用的还是临界区(
Critical Sections
),这里只讨论临界区。不过之前,先说明一下同步(
Synchronous
)和异步(
Asynchronous
)的概念。当程序
1
调用程序
2
时,程序
1
停止不动,直到程序
2
完成回到程序
1
来,程序
1
才继续下去,这就是同步;如果程序
1
调用程序
2
后,自己继续执行下去,那么两者之间就是异步。很明显多线程是一种异步执行方式,
Windows
系统的
SendMessage
和
PostMessage
分别是同步和异步方式。
一个
TRTLCriticalSection
类型的变量代表了一个临界区,引用《
Windows
高级编程》中的话,临界区就好像一个只能进一人的洗手间,后面的人只能排队等到进去的人出来才能进去。让我讲得详细一点,假设你有一个数据结构
A
会被线程
B
和
C
读写,则你可以声明一个
TRTLCriticalSection
变量
CS
,接着调用
InitializeCriticalSection(CS)
,这时你拥有了一个临界区,在
B
和
C
线程对
A
进行读写的地方,都套上如下的代码:
EnterCriticalSection(CS);
try
//
对
A
进行读写
finally
LeaveCriticalSection(CS);
end;
你便能保证同一时间只有一个线程对
A
进行读写。
它的过程是这样的:
线程
B
和
C
一直在运行,某一时刻,
B
和
C
同时要对
A
进行操作,但事实上是不可能同时的,总有一个线程会快一点,假设是
B
吧,
B
线程的第一句便是
EnterCriticalSection(CS)
,使得线程
B
进入了临界区,接着
B
对
A
进行操作。而操作系统总是这样频繁的在不同线程之间切换以制造“多任务”的假象,当
B
对
A
的操作进行到一半时,系统将执行权切换给线程
C
,我们知道
C
也要对
A
进行操作,它的第一句也是
EnterCriticalSection(CS)
,可是进这个“洗手间”时发现里面已经有“人”了,
C
没办法只能一直停在
Enter
的地方等候,过很短的时间,执行权交给了线程
B
,
B
继续对
A
进行操作,如此反复,
B
终于操作完了,最后调用
LeaveCriticalSection(CS)
离开“洗手间”,这时执行权再交到
C
时,
C
发现
B
已经离开临界区,它终于可以进入临界区对于
A
进行操作了,而此时别的线程要对
A
进行操作,也只能等
C
离开“洗手间”。
对于临界区有些地方值得注意:
1.
一个
TRTLCriticalSection
变量代表一个临界区,如果你使用两个这样的变量,则它们是毫不相干的,就像两个“洗手间”一样,对于同一个资源不能起到同步的作用。
2.
不要针对多个资源只声明一个
Critical Section
,这样使每一时刻只能对一个资源进行读写,会对程序的效率有很大的影响。为每个需要同步保护的资源声明一个临界区变量。
3.
需要对资源进行同步保护,则所有对资源操作的地方都要有
Enter
和
Leave
函数,且要有
Try..finally
结构,保证最后能
Leave
。
4.
在临界区里面不要进行非常费时的操作,更重要的防止死锁的发生。
死锁
线程同步虽好,但也带来一些问题,最典型的就是死锁,死锁通常发生在线程之间相互等待的情况下。拿临界区来说,假设有两个临界区,两个线程分别进入一个临界区,接着各自又要进入另一个临界区,这时就会落入“你等我,我等你”的轮回。下面我制造这种场景给你给看看:
unit
Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
const
WM_MYMESSAGE = WM_USER + 001 ;
type
TMyThread1 = class(TThread)
protected
procedure Execute; override;
end;
TMyThread2 = class(TThread)
protected
procedure Execute; override;
end;
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
public
{ Public declarations }
Thd1: TMyThread1;
Thd2: TMyThread2;
end;
var
Form1: TForm1;
CS1, CS2: TRTLCriticalSection;
implementation
{$R *.dfm}
procedure DeadLock(var CSA, CSB: TRTLCriticalSection);
begin
EnterCriticalSection(CSA);
Sleep( 100 );
EnterCriticalSection(CSB);
try
Sleep( 1000 );
finally
LeaveCriticalSection(CSA);
LeaveCriticalSection(CSB);
end;
end ;
{ TMyThread }
procedure TMyThread1.Execute;
begin
DeadLock(CS1, CS2);
end ;
{ TMyThread2 }
procedure TMyThread2.Execute;
begin
DeadLock(CS2, CS1);
end ;
{ TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
begin
Thd1.Resume;
Thd2.Resume;
end ;
procedure TForm1.FormCreate(Sender: TObject);
begin
InitializeCriticalSection(CS1);
InitializeCriticalSection(CS2);
Thd1 := TMyThread1.Create(True);
Thd2 := TMyThread2.Create(True);
end ;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
DeleteCriticalSection(CS1);
DeleteCriticalSection(CS2);
Thd1.Free;
Thd2.Free;
end ;
end .
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
const
WM_MYMESSAGE = WM_USER + 001 ;
type
TMyThread1 = class(TThread)
protected
procedure Execute; override;
end;
TMyThread2 = class(TThread)
protected
procedure Execute; override;
end;
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
public
{ Public declarations }
Thd1: TMyThread1;
Thd2: TMyThread2;
end;
var
Form1: TForm1;
CS1, CS2: TRTLCriticalSection;
implementation
{$R *.dfm}
procedure DeadLock(var CSA, CSB: TRTLCriticalSection);
begin
EnterCriticalSection(CSA);
Sleep( 100 );
EnterCriticalSection(CSB);
try
Sleep( 1000 );
finally
LeaveCriticalSection(CSA);
LeaveCriticalSection(CSB);
end;
end ;
{ TMyThread }
procedure TMyThread1.Execute;
begin
DeadLock(CS1, CS2);
end ;
{ TMyThread2 }
procedure TMyThread2.Execute;
begin
DeadLock(CS2, CS1);
end ;
{ TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
begin
Thd1.Resume;
Thd2.Resume;
end ;
procedure TForm1.FormCreate(Sender: TObject);
begin
InitializeCriticalSection(CS1);
InitializeCriticalSection(CS2);
Thd1 := TMyThread1.Create(True);
Thd2 := TMyThread2.Create(True);
end ;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
DeleteCriticalSection(CS1);
DeleteCriticalSection(CS2);
Thd1.Free;
Thd2.Free;
end ;
end .
运行上面程序,点击按钮
1
,你的程序没有什么异样,但关闭程序看看,程序关闭不了,因为两个线程落到相互等待的局面,而线程的
Free
又在等
Execute
结束,所以程序没有办法关闭了。
让我说说为什么会发生死锁,点击
Button1
时,两个线程同时启动,
TMyThread1
执行了
DeadLock(CS1, CS2)
,而
TMyThread2
执行了
DeadLock(CS2, CS1)
,注意到两个参数倒过来了。
它的过程是这样,首先线程
1
先执行
DeadLock
,第一个进入
EnterCriticalSection(CSA)
,注意这里的
CSA
是
CS1
,接下来
Sleep(100)
,在线程
1
睡觉到一般时,线程
2
得到执行权也执行
DeadLock
,它也进入
EnterCriticalSection(CSA)
,这里的
CSA
却是
CS2
,接着线程
2
也
Sleep(100)
,也是睡到一半,换到线程
1
,线程
1
睡醒后往下执行,它要
Enter
到
CSB
,对于线程
1
来说,
CSB
是
CS2
,但
CS2
已经被线程
2
给
Enter
了,所以线程
1
挂起等待线程
2
离开
CS2
临界区;接着线程
2
醒来要进入
EnterCriticalSection(CSB)
,这里的
CSB
是
CS1
,但
CS1
给线程
1
进入了,所以线程
2
也挂起等待线程
1
离开
CS1
临界区。就这样,两个线程相互等待,没完没了。
这就是死锁发生的情况之一,当多个临界区一起工作时,死锁就有可能发生,这是要特别注意的,在死锁可能发生的地方,尽量只用一个临界区。
后记
关于线程的杂谈就到这里了,这两篇线程的文章更关注于其应用。
如果要学习
Delphi
中的
TThread
对于线程机制的封装,推荐给你一篇技术文章:
http://www.delphibbs.com/keylife/iblog_show.asp?xid=19903
,非常精彩,我在其中也学到很多。
至于要系统学习多线程原理的,首推《
Win32
多线程程序设计》
另外,《
Windows
高级编程》对于线程的篇章也非常好。