Delphi线程基础知识
参考http://blog.chinaunix.net/uid-10535208-id-2949323.html
一、概述
Delphi提供了好几种对象以方便进行多线程编程。多线程应用程序有以下几方面的功能:
1.避免性能瓶颈:单线程应用程序在进行比较慢的操作如磁盘读写的时候,CPU必须停下来等待,直到该操作执行完毕。而多线程应用程序在进行比较慢的操作如磁盘读写的时候,即系执行另一个单独的线程;
2.组织应用才程序的行为:通常,一个程序的行为可以组织成几个功能相互独立的平行的处理过程。将这些独立的处理组织成单独的几个线程,就可以同时启动这几个处理过程。还可以使用线程的优先级来控制那些任务获得更多的CPU时间
二、定义线程对象
在大多数情况下,可以使用线程对象来代表一个线程。线程对象封装了常用的线程操作,从而简化了线程对象
注意:线程对象不允许控制线程的安全属性和栈的大小。如果你确实要控制这些东西,那只能够使用BeginThread函数来创建线程了。
要在程序中使用线程对象,必须自定义一个从TThread派生的类。可以使用想到来帮助创建,生成的代码如下:
unit Unit2; interface
uses
Classes;
type
TMyThread = class(TThread)
private
{Private declarations}
protected
procedure Execute; overrride;
end;
implementation
{TMyThread}
procedure TMyThread.Execute;
begin
{Place thread code here}
end;
end.
在上面自动生成的代码里,可以:
加入自己的线程初始化代码(可选)
将线程执行的操作写入Execute函数
加入自己的线程初始化代码(可选)
三、初始化线程
如果你需要为你新创建的线程类加入初始化代码,就必须重写Create方法。你可以在Create方法里面给线程赋优先级,并可以决定线程在任务完成后是否自动销毁
1.给线程赋一个默认优先级
可以使用Priority属性给线程赋予优先级
在Windows下,Priority可以取下面的值:
tpIdle The thread executes only when the system is idle.Windows won't interrupt other threads to execute thread with tpIdle priority
tpLowest The thread's priority is two points below normal
tpLower The thread's priority is one point below normal
tpNormal The thread has normal priority
tpHigher The thread's priority is one point above normal
tpHighest The thread's priority is two point above normal
tpTimeCritical The thread gets highest priority
下面的代码演示了一个低优先级的线程的构造函数,该线程在后台执行一些任务:
constructor TMyThread.Create(CreateSuspended : Boolean);
begin
inherited Create(CreatedSuspended);
Priority := tpIdle;
end;
2.指示线程是否自动销毁
通常,当一个线程完成了它的操作之后,他们就应该被销毁。如果是这样的话,最好的方式就是让线程自动销毁。要让一个线程自动销毁的话,可以在线程构造函数里面将它的FreeOnTerminate属性设置为True
然而有些时候,一个线程的结束必须和其他线程同步。如你或许正在线程A里等待线程B的返回值,这个时候你肯定不想线程B在线程A收到返回值前就自动销毁。在这种情况下,就必须将线程B的FreeOnTerminate属性设置为Flase,然后再线程A接收到返回值后,显式销毁线程B
四、线程函数
TThread的Execute方法是线程函数。你可以将线程函数看作是一个由你的程序启动的独立程序。只不过它共享你的程序的进程空间。写线程函数比写一个单独的程序要复杂,因为你必须确保线程函数没有覆盖你的程序当中的其他线程所使用的内存区域。从另一个方面看,由于多个线程之间是共享同一个进程空间的,因此你可以在多个线程之间通过共享内存进行交流
当TThread的Create()被调用时,需要传递一个布尔型的参数CreateSuspended。如果把这个参数设成False,那么当调用Create()后,Excute()会被自动地调用,也就是自动地执行线程代码。
如果该参数设为True,则需要运行TThread的Resume()来唤醒线程。一般情况下,当你调用Create()后,还会有一些其他的属性要求设置。所以,应当把CreateSuspended参数设为True,因为在TThread已执行的情况下设置TThread的属性可能会引起麻烦。
再深入一点讲,在构造函数Create()中隐含调用了一个RTL例程BeginThread(),而它又调用了一个API函数CreateThread()来创建一个线程对象的实例。CreateSuspended参数表明是否传递CREATE_ SUSPEDED 标志给CreateThread()
1.使用主线程
当你使用VCL中的对象时,他们的属性和方法都不保证是线程安全的。即访问属性或执行方法时涉及的一些操作或许会用到别的线程可能正在使用的未保护的内存区域。考虑到这一情况,Delphi专门设置了一个主线程来访问VCL对象。主线程处理程序中所有组件接收到的Windows消息。
如果所有的对象都在同一个线程中访问它们的属性以及执行它们的方法,那么你就不必担心这些对象之间互相影响。可是如果你想在主线程以外的线程中访问VCL对象,那怎么办?可以创建一个单独的方法并在这个方法执行访问主线程VCL对象的操作,然后使用Synchronize方法来调用这个方法。如
procedure TMyThread.PushTheButton;
begin
Button1.Click;
end; procedure TMyThread.Execute;
begin
...
Synchronize(PushTheButton); //注意这个函数的使用的形式
...
end;
Synchronize会等待主线程进入消息循环时才执行传入的方法。
当你确信某个对象的方法是线程安全的时候,可以省略Synchronize,这样可以提高程序的效率,因为不必等待主线程进入消息队列。
建议在主线程中周期的调用CheckSynchronize,以便后台线程能够执行它们的Synchronize方法。
当然调用CheckSynchronize方法的最佳时机应当是当主线程处于Idle时(可以在OnIdle事件处理程序中调用CheckSynchronize)
2.使用线程局部变量
线程函数以及在线程函数里面调用的任何其他的函数都可以定义它们自己的局部变量,也可以访问任何全局变量。事实上,全局变量是在多个线程之间进行交流的一个很有效的手段。
有些时候,你可以需要这样的一些变量:同一个线程对象里面的所有函数都可以访问,但是其他的线程对象里面的函数则不能访问。可以声明线程局部变量,线程局部变量是在threadvar程序段进行声明的,如
threadvar
x : Integer;
threadvar程序段只能声明全局变量。指针和函数变量不能成为线程变量。使用了copy-on-write技术的类型(如long strings)也不能声明为线程变量。
3.Terminate
你可以允许其他线程来向一个线程发出结束的信号。当其他的线程想要结束该线程时,可以调用它的Terminate方法。Terminate方法将Terminated属性设置为True。如
procedure TMyThread.Execute;
begin
while not Terminated do
PerformSomeTask;
end;
4.在线程函数里面处理异常
Execute函数必须捕获其中发生的所有异常。如果你在线程函数里面漏掉了某个异常的话,那么应用程序就会出现非法访问的情况。
要捕获线程函数里面所有的异常,可以在Execute方法里面使用try...except语句块,如:
procedure TMyThread.Execute;
begin
try
while not Terminated do
PerformSomeTask;
except
{do something with exceptions}
end;
end;
五、销毁线程
在线程结束的时候,会触发OnTerminate事件。可以将clean-up代码放在OnTerminate事件的处理程序里。OnTerminate事件处理程序不是作为线程的一部分来运行,而是运行在主线程的上下文中。因此:
你不能在OnTerminate事件处理程序中使用线程变量
你可以在OnTerminate事件处理程序中安全的访问任何VCL对象
六、多线程并发执行
当你在写线程代码的时候,必须要考虑到其他线程可能会与当前线程同时执行这一事实。尤其是需要避免多个线程同时访问相同的全局变量。此外,一个线程里面的代码可能会依赖其他线程的执行结果。
1.避免同时访问
在访问全局对象时候,为了避免和其他的线程发生冲突,需要阻塞其他线程的操作直到当前线程的访问代码执行完毕。需要注意不要无谓的阻塞其他线程的操作,阻塞操作会极大降低程序的执行效率。无谓的阻塞会有被使用线程的初衷。
VCL支持三种方法来组织其他线程访问当前线程正在访问的内存区域:锁定对象、使用关键区、使用multi-read exclusive-write Synchronizer
1)锁定对象
一些对象有内置的锁机制。比如,Canvas对象就有一个Lock方法,可以组织其他线程的访问,直到调用UnLock方法
2)关键段
如果对象没有提供内置的锁机制,你可以使用关键段。关键段就像一个们,同一时间只允许一个线程进入。要使用关键段,先创建一个TCriticalSection类的全局对象。TCriticalSection有两个方法,Acquire(阻塞其他方法)和Release(移除阻塞)
每个关键段与你要保护的全局区域相关。每个访问全局内存区域的线程都必须首先使用Acquire方法来保证没有其他别的线程在使用它。当访问结束之后,线程调用Release方法以便其他的线程能够通过Acquire方法来访问该全局内存区域
注意:关键段只能在所有的线程都使用它们时才起作用。如果有线程忽略关键段,不通过Acquire访问内存区域,则会导致并发问题。
2.等待其他线程
如果当前线程需要等待另一个线程完成某个任务,你可以让当前线程暂停执行
1)等待另一个线程结束
可以使用WaitFor方法等待另一个线程结束。WaitFor直到另一个线程结束后才返回。如:
if ListFillingThread.WaitFor then
begin
with ThreadList1.LockList do
begin
for I :=0 to Count -1 do
ProcessItem(Items[I]);
end;
ThreadList1.UnlockList;
end;
· 2)等待一个任务结束
有时候,需要等待另一个线程执行完某些操作之后而不是完全结束。为此,应当使用一个事件对象。事件对象(TEvent)应当在全局范围内被创建以便它们能够被所有的线程访问
当一个线程完成了一个操作,并且该操作是其他线程所依赖,那么它就调用TEvent.SetEvent打开信号,于是所有的其他线程都可以通过检测直到该操作执行完毕了。要关闭信号,使用ReseEvent操作
七、执行线程对象
启动和停止线程
一个线程在使用它的Create()方法进行初始化的时候,如果传入的参数是Fasle,那么该线程的Execute()方法就会被调用,也就是说使用Create(False)方法之后,这个线程就已经跑起来了。
一个线程在它完成之前,可以被停止和启动任意次。临时停止一个线程,使用Suspend函数。调用Resume方法恢复线程。Suspend函数会增加内部计数器,而Resume则会减少内部计数器,因此你可以嵌套多次调用Suspend和Resume。调用Resume只有当线程计数器为0时,线程才会真正启动起来。
可以调用线程的Terminate方法来永久的终止线程。Terminate设置线程的Terminated属性为True
八、调试多线程应用程序
当调试多线程应用程序的时候,跟踪所有同时执行的线程状态,甚至只是想知道在断点处哪一个线程还在执行都是一件很郁闷的事情,可以使用Thread status Box来帮助跟踪和操纵应用程序的所有线程。要显示Thread status Box,从主菜单选择View|Threads
当一个调试事件(断点、异常、暂停……)发生的额时候,Thread status Box将会指示各个线程的状态。右击Thread status Box来访问……
Thread status Box按照线程ID列出了程序中的所有线程。如果你使用线程对象,那么线程ID是属性ThreadID的值。如果使用的不是线程对象,那么线程ID是BeginThread函数的返回值
Delphi线程基础知识的更多相关文章
- Java__线程---基础知识全面实战---坦克大战系列为例
今天想将自己去年自己编写的坦克大战的代码与大家分享一下,主要面向学习过java但对java运用并不是很熟悉的同学,该编程代码基本上涉及了java基础知识的各个方面,大家可以通过练习该程序对自己的jav ...
- java线程基础知识----线程与锁
我们上一章已经谈到java线程的基础知识,我们学习了Thread的基础知识,今天我们开始学习java线程和锁. 1. 首先我们应该了解一下Object类的一些性质以其方法,首先我们知道Object类的 ...
- java线程基础知识----线程基础知识
不知道从什么时候开始,学习知识变成了一个短期记忆的过程,总是容易忘记自己当初学懂的知识(fuck!),不知道是自己没有经常使用还是当初理解的不够深入.今天准备再对java的线程进行一下系统的学习,希望 ...
- Windows核心编程 第六章 线程基础知识 (上)
第6章 线程的基础知识 理解线程是非常关键的,因为每个进程至少需要一个线程.本章将更加详细地介绍线程的知识.尤其是要讲述进程与线程之间存在多大的差别,它们各自具有什么作用.还要介绍系统如何使用线程内核 ...
- Java并发之线程管理(线程基础知识)
因为书中涵盖的知识点比较全,所以就以书中的目录来学习和记录.当然,学习书中知识的时候自己的思考和实践是最重要的.说到线程,脑子里大概知道是个什么东西,但很多东西都还是懵懵懂懂,这是最可怕的.所以想着细 ...
- Java线程基础知识(状态、共享与协作)
1.基础概念 CPU核心数和线程数的关系 核心数:线程数=1:1 ;使用了超线程技术后---> 1:2 CPU时间片轮转机制 又称RR调度,会导致上下文切换 什么是进程和线程 进程:程序运行资源 ...
- java线程基础知识----java daemon线程
java线程是一个运用很广泛的重点知识,我们很有必要了解java的daemon线程. 1.首先我们必须清楚的认识到java的线程分为两类: 用户线程和daemon线程 A. 用户线程: 用户线程可以简 ...
- java并发编程(一)----线程基础知识
在任何的生产环境中我们都不可逃避并发这个问题,多线程作为并发问题的技术支持让我们不得不去了解.这一块知识就像一个大蛋糕一样等着我们去分享,抱着学习的心态,记录下自己对并发的认识. 1.线程的状态: 线 ...
- Java 线程基础知识
前言 什么是线程?线程,有时被称为轻量进程(Lightweight Process,LWP),是程序执行流的最小单元.一个标准的线程由线程 ID,当前指令指针 (PC),寄存器集合和堆栈组成.另外,线 ...
随机推荐
- webpack的最简单应用,只使用js与css的打包
首先进行全局安装webpack npm install -g webpack cmd跳转到项目的文件夹,安装webpack npm install --save-dev webpack 接着需要pac ...
- ubuntu14 谷歌输入法
sudo apt-get install ibus-googlepinyin 装完重启即可: (在右上角语言处右键,添加text entry)
- ndk学习19: 使用Eclipse调试so
1. 设置调试选项 在AndroidManifest文件加入允许调试 android:debuggable="true" 此时编译项目会多出: 2. 配置调试代码 把需要调 ...
- 【GoLang】并发小结
006.并发 1 概念 1.1 goroutine是Go并行设计的核心,goroutine的本质是轻量级线程 1.2 golang的runtime实现了对轻量级线程即goroutine的智能调度管理 ...
- Android常用控件
Android 中使用各种控件(View) DatePicker - 日期选择控件 TimePicker - 时间选择控件 ToggleButton - 双状态按钮控件 EditText - 可编辑 ...
- DELPHI控件:DBLookupComboBOX组件的使用方法
在许多数据表中,数据是以代码方式存放的,如在班级编码数据表tB03(表5.5)中,系部字段TB0309采用编码方式存放,系部真实名称则存放在系部编码表TB06.使用代码的好处是,用户可在编码表TB06 ...
- java web 学习 --第八天(Java三级考试)
第七天的学习内容:http://www.cnblogs.com/tobecrazy/p/3464231.html EL表达式 EL : Expression Language 使用EL表达式可以减少& ...
- 关于TortoiseSVN的一些知识
TortoiseSVN的一切命令都是相对于它自己来说的 1.Import,导入,指的是导入SVN的代码库,即Repository. 2.Export,导出,指的是将代码从Repository中导出到你 ...
- Centos 用户登录失败N次后锁定用户禁止登陆
针对linux上的用户,如果用户连续3次登录失败,就锁定该用户,几分钟后该用户再自动解锁 Linux有一个pam_tally2.so的PAM模块,来限定用户的登录失败次数,如果次数达到设置的阈值,则锁 ...
- tomcat URL乱码问题
用get传参时,显示乱码 在tomcat里的server.xml中添加一下即可. <Connector port="8080" protocol="HTTP/1.1 ...