`
isiqi
  • 浏览: 16622209 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

线程杂谈

阅读更多

前言

由于项目的关系,所以和线程的接触颇多,常常遇到问题,常常看TThread的代码,又常常想一些办法来解决遇到的问题,所以就有了这篇文章。

正文

我们常有工作线程和主线程之分,工作线程负责作一些后台操作,比如接收邮件;主线程负责界面上的一些显示。工作线程的好处在某些时候是不言而喻的,你的主界面可以响应任何操作,而背后的线程却在默默地工作。

VCL中,工作线程执行在Execute方法中,你必须从TThread继承一个类并覆盖Execute方法,在这个方法中,所有代码都是在另一个线程中执行的,除此之外,你的线程类的其他方法都在主线程执行,包括构造方法,析构方法,Resume等,很多人常常忽略了这一点。

最简单的一个线程类如下:

TMyThread = class(TThread)

protected

procedure Execute; override;

end;

Execute中的代码,有一个技术要点,如果你的代码执行时间很短,像这样,Sleep(1000),那没有关系;如果是这样Sleep(10000)10秒,那么你就不能直接这样写了,须把这10秒拆分成101秒,然后判断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事件发生后,线程类不一定会释放,只有在FreeOnTerminateTrue之后,才会Thread.Free。看一下ThreadProc函数就知道。

依照Onterminate事件,我们可以设计自己的异步事件。

Synchronize方法只能传进一个无参数的方法类型,但我们的事件经常是要带一些参数的,这个稍加思考就可以得到解决,即在线程类中保存参数,触发事件前先设置参数,再调用异步事件,参数复杂的可以用记录或者类来实现。

假设这样,上面的代码每睡一秒,线程即向外面引发一次事件,我们的类可以这样设计:

TSecondEvent=procedure(Second:Integer)ofobject;
TMyThread=
class(TThread)
private
FSecond:Integer;
FSecondEvent:TSecondEvent;
procedureCallSecondEvent;
protected
procedureExecute;override;
public
propertySencondEvent:TSecondEventreadFSecondEvent
writeFSecondEvent;
end;

{TMyThread}

procedureTMyThread.CallSecondEvent;
begin
ifAssigned(FSecondEvent)then
FSecondEvent(FSecond);
end;

procedureTMyThread.Execute;
var
i:Integer;
begin
fori:=0to9do
ifnotTerminatedthen
begin
Sleep(
1000);
FSecond:=i;
Synchronize(CallSecondEvent);
end
else
Break;
end;

在主窗体中假设我们这样操作线程:

procedureTForm1.Button1Click(Sender:TObject);
begin
MyThread:=TMyThread.Create(true);
MyThread.OnTerminate:=ThreadTerminate;
MyThread.SencondEvent:=SecondEvent;
MyThread.Resume;
end;

procedureTForm1.ThreadTerminate(Sender:TObject);
begin
ShowMessage(
'ok');
end;

procedureTForm1.SecondEvent(Second:Integer);
begin
Edit1.Text:=IntToStr(Second);
end;

我们将每隔一秒就得到一次通知并在Edit中显示出来。

现在我们已经知道如何正确使用Execute方法,以及如何在主线程与工作线程之间通信了。但问题还没有结束,有一种情况出乎我的意料之外,即如果线程中有一些资源,Execute正在使用这些资源,而主线程要释放这个线程,这个线程在释放的过程中会释放掉资源。想想会不会有问题呢,两个线程,一个在使用资源,一个在释放资源,会出现什么情况呢,

用下面代码来说明:

type
TMyClass=
class
private
FSecond:Integer;
public
procedureSleepOneSecond;
end;

TMyThread=
class(TThread)
private
FMyClass:TMyClass;
protected
procedureExecute;override;
public
constructorMyCreate(CreateSuspended:Boolean);
destructorDestroy;override;
end;

implementation

{TMyThread}

constructorTMyThread.MyCreate(CreateSuspended:Boolean);
begin
inheritedCreate(CreateSuspended);
FMyClass:=TMyClass.Create;
end;

destructorTMyThread.Destroy;
begin
FMyClass.Free;
FMyClass:=
nil;
inherited;
end;

procedureTMyThread.Execute;
var
i:Integer;
begin
fori:=0to9do
FMyClass.SleepOneSecond;
end;

{TMyClass}

procedureTMyClass.SleepOneSecond;
begin
FSecond:=
0;
Sleep(
1000);
end;

end.

用下面的代码来调用上面的类:

procedureTForm1.Button1Click(Sender:TObject);
begin
MyThread:=TMyThread.MyCreate(true);
MyThread.OnTerminate:=ThreadTerminate;
MyThread.Resume;
end;

procedureTForm1.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就出错了。

分享到:
评论

相关推荐

    多核多线程杂谈-并行计算

    ### 多核多线程杂谈-并行计算 #### 1. 并行计算概述 随着计算机硬件的发展,单核处理器的性能提升遇到了物理瓶颈,因此多核处理器成为了提高计算能力的关键技术之一。并行计算是利用多核处理器或多台计算机协同...

    炉边夜话---多核多线程杂谈

    - **线程调度与优先级**:操作系统负责管理线程的调度,包括确定哪些线程可以在哪个时刻运行。线程的优先级会影响其被调度的概率,合理的优先级设置可以改善程序的响应时间和整体性能。 - **性能瓶颈分析**:多线程...

    多线程编程指南 SUN出品

    《多线程编程指南》是由SUN公司出版的一本深入探讨多线程编程的重要书籍,对于想要提升在并发处理方面技能的程序员来说,这是一份不可多得的学习资源。本书全面讲解了Java语言中的线程相关知识,涵盖了从基本概念到...

    java陷阱--面试(题集)杂谈

    第二十六,当一个线程进入对象的synchronized方法,其他线程不能进入该对象的其他synchronized方法,除非当前线程退出了同步方法。 第二十七,try后的finally块总会被执行,无论是否有return语句。return前执行...

    swing开发杂谈--初版本程序源码

    在"swing开发杂谈--初版本程序源码"中,可能包含了上述部分或全部知识点的实际应用,通过分析`netHelper`这个子文件夹,我们可以看到可能的网络辅助类或其他功能模块的实现。这个源码可能会演示如何使用Swing构建一...

    杂谈,一些工具类的集合

    标题和描述中的"杂谈,一些工具类的集合"可能指的是一个涵盖多种工具的资源包,旨在解决日常开发中的各种问题。 首先,我们来探讨一下工具类在编程中的作用。在编程中,工具类通常是一组静态方法的集合,这些方法...

    杂谈PHP4内核Hacking .pdf

    2. **TSRM的工作原理**:TSRM通过维护变量的中立性来确保线程安全,这意味着即使在高并发情况下,每个请求仍然能够正确地访问其所需的数据,而不会与其他请求混淆。 3. **使用TSRM**:在PHP内核中,从全局传递参数时...

    高并发场景杂谈.zip

    "高并发场景杂谈.zip"这个压缩包文件集成了多种处理高并发问题的策略和技术,旨在为开发者提供解决高并发问题的思路和实践案例。下面将详细讨论其中涉及的知识点。 首先,我们来看"Redis专场:如何利用Redisson...

    Android开发杂谈

    本文将基于“Android开发杂谈”的主题,结合提供的资源——一个名为"Android_.pdf"的文件,来深入探讨一些重要的知识点。 1. **源码阅读**: 在Android开发中,理解源码是提升技能的关键。Android开源项目(AOSP)...

    充分利用CPU多核心并发特性,创建多个pigz线程, 并将pigz的每个线程绑定的固定核心, 实现高性能 压缩和压缩

    linux

    关于Java的几个经典问题

    (五)——传了值还是传了引用(六)——字符串(String)杂谈 (七)——日期和时间的处理 (八)——聊聊基本类型(内置类型)(九)——继承、多态、重载和重写(十)——话说多线程 (十一)——这些运算符你是否...

    java杂谈-一个计算机专业学生几年的编程经验汇总谈.pdf

    `client`版本通常适用于内存有限的桌面环境,而`server`版本则针对服务器环境,优化了多线程和大内存应用的性能。 理解这些基础知识对于Java程序员来说至关重要,它们不仅能够帮助我们编写更高效的代码,还能让我们...

    Java面试题解惑系列

    来自网络,主要包括以下内容:1、类初始化的顺序;...6.String杂谈;7.日期与时间的处理;8.基本类型总结;9.继承,多态,重载,重写;10.多线程;11.运算符总结。 适合将要笔试面试Java的朋友参考。

    JAVA面试题解惑系列合集

    6. 字符串(String)杂谈:字符串是JAVA中使用最频繁的类之一。JAVA提供了String类来创建和操作字符串。由于String对象的不可变性,对于字符串的任何操作都会返回一个新的String对象。 7. 日期和时间的处理:JAVA中...

    深入理解jvm视频资源

    杂谈.mp4**:这个视频可能包含JVM领域的各种实用技巧、最佳实践和最新动态,比如JIT编译器的工作原理、JVM的并发优化、JVM与其他语言虚拟机的比较等。 5. **1.说在前面的话.mp4**:视频开头可能会介绍学习JVM的重要...

    JAVA面试题解惑系列

    6. **字符串(String)杂谈**: 包括String的不可变性、String池的概念、字符串比较(equals vs ==)等常见面试话题。String的不可变性意味着一旦创建,就不能改变,这涉及到了内存管理和性能优化。 7. **日期和...

    臧圩人--JAVA面试题解惑系列合集.pdf

    **1.6 JAVA面试题解惑系列(六)——字符串(String)杂谈** - **知识点**:深入探讨字符串处理技术,包括字符串拼接、比较、格式化等常见操作的内部实现,以及性能优化建议。 **1.7 JAVA面试题解惑系列(七)——...

Global site tag (gtag.js) - Google Analytics