Delphi中多线程分析详解
Delphi多线程同步过程Synchronize说明个人见解
Delphi多线程同步过程Synchronize说明个人见解Delphi多线程同步过程Synchronize说明个人见解在windows原生应用程序开发中,经常伴随多线程的使用,多线程开发很简单,难点就是在于线程的同步,在Delphi中提供了VC中不具备的一个过程Synchronize,使用起来非常方便,解决了很多VC 开发中碰到的常见问题,但是在看了很多Delphi代码后,发现很多人对于Synchronize的理解还是有问题的,不能很好地正确使用Synchronize过程,本文对Synchronize过程的使用提出一些个人的见解,供大家参考。
在VC中使用多线程,由于MFC或VC本身的特点,VC人员在更新窗口界面和工作线程的关系上分的是比较清楚的,但是Delphi里由于VCL提供的方便特性,造成大量人员在工作线程中更新窗口界面,给系统稳定带来潜在的危险,应该说比较严格的方法应该是永远让更新窗口界面的工作仅由主线程完成,工作线程仅仅做后台的一些工作,当工作线程的结果需要反馈到界面上来的时候,应该使用各种同步对象(临界区、互斥量)等来进行同步,然后让主线程更新窗口界面。
这里由于主要讲解Synchronize的使用,使用同步对象更新的方法就不说了,首先说一下在VC中比较接近Synchronize过程的SendMessage函数,其实在Delphi中也可以使用SendMessage来实现线程同步,实现通知主线程更新窗口界面,只是没有Synchronize过程用起来那么方便。
SendMessage函数的工作过程是比较复杂的,有兴趣的朋友可以多查查SendMessage函数及其同多线程的关系,如果在工作线程中调用SendMessage函数,工作线程会挂起,然后等待主线程空闲,主线程空闲开始处理接收到的消息,处理完成把结果返回,工作线程接收到返回结果后继续运行,也就是消息处理函数是在主线程中运行的,而且工作线程会挂起等待SendMessage函数返回即消息处理函数处理完成才会继续往下执行,在VC开发中这种方式也是经常被使用的。
Delphi多线程编程-编程技巧文章-蓝鸟软件-15
Delphi多线程编程-编程技巧文章-蓝鸟软件-15多线程编程(15) - 多线程同步之WaitableTimer (等待定时器对象)[续]。
本次专门研究下 SetWaitableTimer 的第二个参数(起始时间).它有正值、负值、0值三种情况, 前面已用过 0值.先学习负值(相对时间), 也就是从当前算起隔多长时间开始执行.这个相对时间是已1/100 纳秒为单位的, 譬如赋值3*10000000 相当于 3 秒.1 s(秒) = 1,000 ms(毫秒);1 s(秒) = 1,000,000 μs(微妙);1 s(秒) = 1,000,000,000 ns(纳秒);1 s(秒) = 1,000,000,000,000 ps(皮秒);本例效果图:多线程编程(15) - 多线程同步之WaitableTimer (等待定时器对象)[续]代码文件:unit Unit1;interfaceusesWindows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,Dialogs, StdCtrls;typeTForm1 = class(TForm)Button1: TButton;procedure Button1Click(Sender: TObject);procedure FormDestroy(Sender: TObject);end;varForm1: TForm1;implementation{$R *.dfm}varf: Integer;hWaitableTimer: THandle;function MyThreadFun(p: Pointer): DWORD; stdcall;vari,y: Integer;beginInc(f);y := 20 * f;if WaitForSingleObject(hWaitableTimer, INFINITE) = WAIT_OBJECT_0 thenbeginfor i := 0 to 1000 dobeginForm1.Canvas.Lock;Form1.Canvas.TextOut(20, y, IntToStr(i));Form1.Canvas.Unlock;Sleep(1);end;end;Result := 0;end;procedure TForm1.Button1Click(Sender: TObject);varThreadID: DWORD;DueTime: Int64;beginhWaitableTimer := CreateWaitableTimer(nil, True, nil); DueTime := -3*10000000; {3秒钟后执行} SetWaitableTimer(hWaitableTimer, DueTime, 0, nil, nil, False); Repaint; f := 0;CreateThread(nil, 0, @MyThreadFun, nil, 0, ThreadID); CreateThread(nil, 0, @MyThreadFun, nil, 0, ThreadID); CreateThread(nil, 0, @MyThreadFun, nil, 0, ThreadID); end;procedure TForm1.FormDestroy(Sender: TObject);beginCloseHandle(hWaitableTimer);end;end.窗体文件:object Form1: TForm1Left = 0Top = 0Caption = 'Form1'ClientHeight = 116ClientWidth = 179Color = clBtnFaceFont.Charset = DEFAULT_CHARSETFont.Color = clWindowTextFont.Height = -11 = 'Tahoma'Font.Style = []OldCreateOrder = FalseOnDestroy = FormDestroyPixelsPerInch = 96TextHeight = 13object Button1: TButtonLeft = 96Top = 83Width = 75Height = 25Caption = 'Button1'TabOrder = 0OnClick = Button1Clickendend当我们需要一个绝对时间时, 譬如2009-2-18 13:10:5, 函数需要的 Int64 值应该是个 TFileTime 格式的时间.先看三种相关时间类型(TFileTime、TSystemTime、TDateTime)的定义:TFileTime(又名 FILETIME 或 _FILETIME)_FILETIME = recorddwLowDateTime: DWORD;dwHighDateTime: DWORD;end;TSystemTime(又名 SYSTEMTIME 或 _SYSTEMTIME)_SYSTEMTIME = recordwYear: Word;wMonth: Word;wDayOfWeek: Word;wDay: Word;wHour: Word;wMinute: Word;wSecond: Word;wMilliseconds: Word;end;TDateTime = type Double;//TFileTime 相当于一个Int64, 一般要通过给TSystemTime 或TDateTime 赋值, 然后转换过去.//在例子中我是通过下面过程转过去的:StrToDateTime -> DateTimeT oSystemTime -> SystemTimeT oFileTime -> LocalFileTimeToFileTime下面程序指定在2009年2月18号下午1点10分5秒时运行三个线程(窗体同上, 我已找了个合适的时间测试成功).unit Unit1;interfaceusesWindows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,Dialogs, StdCtrls;typeTForm1 = class(TForm)Button1: TButton;procedure Button1Click(Sender: TObject);procedure FormDestroy(Sender: TObject);end;varForm1: TForm1;implementation{$R *.dfm}varf: Integer;hWaitableTimer: THandle;function MyThreadFun(p: Pointer): DWORD; stdcall;vari,y: Integer;beginInc(f);y := 20 * f;if WaitForSingleObject(hWaitableTimer, INFINITE) = WAIT_OBJECT_0 thenbeginfor i := 0 to 1000 dobeginForm1.Canvas.Lock;Form1.Canvas.TextOut(20, y, IntToStr(i));Form1.Canvas.Unlock;Sleep(1);end;end;Result := 0;end;procedure TForm1.Button1Click(Sender: TObject);conststrTime = '2009-2-18 13:10:5';varThreadID: DWORD;DueTime: Int64;st: TSystemTime;ft,UTC: TFileTime;dt: TDateTime;beginDateTimeToSystemTime(StrT oDateTime(strTime), st); {从TDateTime 到 TSystemTime}SystemTimeT oFileTime(st, ft);{从TSystemTime 到 TFileTime}LocalFileTimeT oFileTime(ft, UTC); {从本地时间到国际标准时间 UTC}DueTime := Int64(UTC); {函数需要的是 Int64}hWaitableTimer := CreateWaitableTimer(nil, True, nil);SetWaitableTimer(hWaitableTimer, DueTime, 0, nil, nil, False);Repaint; f := 0;CreateThread(nil, 0, @MyThreadFun, nil, 0, ThreadID);CreateThread(nil, 0, @MyThreadFun, nil, 0, ThreadID);CreateThread(nil, 0, @MyThreadFun, nil, 0, ThreadID);end;procedure TForm1.FormDestroy(Sender: TObject);beginCloseHandle(hWaitableTimer);end;end.接下来该是 WaitableTimer 对象的回调函数了。
delphi7 serversocket的多线程模式的用法
delphi7 serversocket的多线程模式的用法在 Delphi 7 中,使用 ServerSocket 组件实现多线程模式可以通过以下步骤进行:1. 在 Delphi 7 的主界面上,双击 "ServerSocket" 组件,将其添加到窗体上。
2. 在 "ServerSocket1" 组件的 "Properties" 属性中,设置"Active" 属性为 True,启用服务器端。
3. 在 "ServerSocket1" 组件的 "Properties" 属性中,设置 "Port" 属性为服务器监听的端口号。
例如,设置为 1234。
4. 在 "ServerSocket1" 组件的 "Events" 事件属性中添加以下代码,实现多线程处理客户端连接请求:```procedure TForm1.ServerSocket1ClientConnect(Sender: TObject; Socket: TCustomWinSocket);begin// 创建一个线程处理客户端连接TMyThread.Create(Socket);end;```5. 创建一个继承自 TThread 的自定义线程类,用于处理客户端连接和通信。
代码示例如下:```typeTMyThread = class(TThread)privateSocket: TCustomWinSocket;publicconstructor Create(ASocket: TCustomWinSocket);procedure Execute; override;end;constructor TMyThread.Create(ASocket: TCustomWinSocket); begininherited Create(False);Socket := ASocket;end;procedure TMyThread.Execute;varBuffer: array[0..1023] of AnsiChar;BytesReceived: Integer;begin// 处理客户端连接trywhile not Terminated dobegin// 接收客户端发送的数据BytesReceived := Socket.ReceiveBuf(Buffer, SizeOf(Buffer) - 1);Buffer[BytesReceived] := #0; // 末尾添加字符串结束标志// 在主线程执行 GUI 操作(如果需要)Synchronize(procedurebegin// 在此处更新界面或执行其他需要在主线程执行的操作 // Example: Memo1.Lines.Add(Buffer);end);// 处理接收到的数据// ...end;finally// 关闭客户端连接Socket.Close;Socket.Free;end;end;```在上述代码中,TMyThread 类继承自 TThread,通过重写Execute 方法,实现在独立线程中处理客户端连接和通信的逻辑。
Delphi多线程介绍,以及线程类TThread分析
Delphi 多线程介绍,以及线程类TThread 分析Delphi 中有一个线程类TThread 用来实现多线程编程TThread 类的几个成员作一简单介绍,再说明一下Execute 的实现和Synchronize 的用法就完了。
然而这并不是多线程编程的全部,我写此文的目的在于对此作一个补充。
线程 本质上是进程中一段 并发 运行的代码。
一个进程至少有一个线程,即所谓的主线程。
同时还可以有多个子线程。
当一个进程中用到超过一个线程时,就是所谓的“多线程”。
1、CreateThread 、long _beginthread 、BeginThread 介绍 用Windows API 来创建线程,API 函数 CreateThread 的定义原型:1 2 3 4 5 6 7 8 HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, //线程属性(用于在NT 下进行线程的安全属性设置,在9X 下无效) DWORD dwStackSize, //堆栈大小 LPTHREAD_START_ROUTINE lpStartAddress, //起始地址,即线程函数的入口,直至线程函数结束,线程结束 LPVOID lpParameter, //参数 DWORD dwCreationFlags, //创建标志(用于设置线程创建时的状态) LPDWORD lpThreadId //线程ID ); //最后返回线程HandleCreateThread 参数很多,而且在C Runtime Library 里提供了一个通用的线程函数(理论上可以在任何支持线程的OS 中使用):1 unsigned long _beginthread(void (_USERENTRY *__start)(void *), unsigned __stksize, void *__arg);Delphi 也提供了一个相同功能的类似函数:1 f unction BeginThread(2 3 4 5 6 7 8 SecurityAttributes: Pointer;StackSize: LongWord;ThreadFunc: TThreadFunc;Parameter: Pointer;CreationFlags: LongWord;var ThreadId: LongWord): Integer;这三个函数的功能是基本相同的,它们都是将线程函数中的代码放到一个独立的线程中执行。
DELPHI多线程
是不同的,在并行运算时容易判断出是两个不同的程序在运行。
假定我们给两个线程对象起的名字是:
WIN 98/NT/2000/XP 是个多任务操作系统,也就是:一个进程可以划分为多个线程,每个线程轮流占用CPU 运行时间和资源,或者说,把CPU 时间划成片,每个片分给不同的线程,这样,每个线程轮流的“挂起”和“唤醒”,由于时间片很小,给人的感觉是同时运行的。
多线程带来如下好处:(自己阅读)
例如Tthread 对象方法内部调用API 函数的时候,一般使用推荐的默认值,但需要更精细的控制时,就可以直接使用API 函数。
其实,Tthread 对象方法已经受到了大多数程序设计者的认可,比如,原来VB是不具备直接处理多线程的能力的,但是,现在就宣称,它具备了简单处理多线程问题的能力,这就很说明问题。
CREATE_SUSPENDED 创建一个挂起的线程;
0 创建后立即激活。
书上有这个函数应用的十分清晰的例子,可以自己阅读。
一般并不推荐使用 CreateTheard函数,而推荐使用RTL 库里的System单元中定义的 BeginTheard函数,因为这除了能创建一个线程和一个入口函数以外,还增加了几项保护措施,具体的请参阅书上的第10页说明。
mymath1
mymath2
这样在Unit1,应该作如下声明:
implementation
{$R *.DFM}
uses unit2,unit3;
var thread1:mymath1;
delphi线程教学第一节:初识多线程
delphi线程教学第⼀节:初识多线程第⼀节:初识多线程1.为什么要学习多线程编程?多线程(多个线程同时运⾏)编程,亦可称之为异步编程。
有了多线程,主界⾯才不会因为耗时代码⽽造成“假死“状态。
有了多线程,才能使多个任务同时执⾏,最⼤化利⽤CPU资源,提⾼效率。
在安卓编程中,要求必须是多线程,主界⾯中的代码只要耗时⼏秒钟,就会触发 ANR 错误。
多线程编程才是⼯作中的常态。
多线程是必须掌握的!越早越好!2.⽹络上 delphi 多线程 Demo 的误区采⽤ API 来实现多线程。
难度太⾼,尤其是⽤指针来传参数,不适合初学者。
⽤访问界⾯来举例,出发点就错了。
多线程最不擅长的就是操作UI。
⽹上流⾏的 demo 就是弄个⼤循环,再来⼀个 TextOut 输出。
结果⼜不得不加上 canvas.Lock,真是够折腾。
3.主线程的定义假设,⼀个EXE程序,拥有⼀个 FrmMain (TForm)。
FrmMain 上⾯有⼀个 Button1 (TButton) ,⼀个Edit1 (TEdit)以及⼀个 Timer1 (TTimer) 。
那么,我们通常把界⾯(UI)定义为主线程,即 FrmMain 就是主线程。
Button1 的 OnClick 事件中的代码运⾏于主线程时空。
(本教程均统⼀定义线程时空⼀词)使⽤者在 Edit1 中的输⼊操作也是主线程时空。
Timer1 的 OnTimer 事件中的代码也运⾏于主线程时空。
请注意:初学者最容易把 OnTimer 事件误认为是多线程时空。
4.普通编程与多线程编程的区别// 普通编程function Accumulate(num:integer):integer;vari:integer;beginresult:=0;if num<1then exit;for i:=1to num doresult:=result+i;end;// 在 Button1 的 OnClick 事件中编写如下代码:varn,Total:integer;beginn:=100;Total:=Accumelate(n);// 等待计算结果,假设计算需要5分钟,此处就得等待5分钟。
WIN32下DELPHI中的多线程
WIN32下DELPHI中的多线程【深入VCL源码】(一)线程的基础知识线程的组成。
线程有两部分组成。
1、一个是线程的内核对象,操作系统用它来对线程实施管理。
内核对象也是系统用来存放线程统计信息的地方。
2、另一个是线程堆栈,它用于维护线程在执行代码时需要的所有函数参数和局部变量。
进程从来不执行任何东西,它只是线程的容器。
线程总是在某个进程环境中创建的,而且它的整个寿命期都在该进程中。
这意味着线程在它的进程地址空间中执行代码,并且在进程的地址空间中对数据进行操作。
因此,如果在单进程环境中,你有两个或多个线程正在运行,那么这两个线程将共享单个地址空间。
这些线程能够执行相同的代码,对相同的数据进行操作。
这些线程还能共享内核对象句柄,因为句柄表依赖于每个进程而不是每个线程存在。
线程是一种操作系统对象,它表示在进程中代码的一条执行路径。
在每一个Wi n32的应用程序中都至少有一个线程,它通常被称为主线程或默认线程。
在应用程序中也可以自由地创建别的线程去执行其他任务。
线程技术使不同的代码可以同时运行。
当然,只有在多C P U的计算机上,多个线程才能够真正地同时运行。
在单个CPU上,由于操作系统把C P U的时间分成很短的片段分配给每个线程,这样给人的感觉好像是多个线程真的同时运行,他们只是“看起来”同时在运行。
Win32是一种抢占式操作系统,操作系统负责管理哪个线程在什么时候执行。
如果当线程1暂停执行时,线程2才有机会获得C P U时间,我们说线程1是抢占的。
如果某个线程的代码陷入死循环,这并不可怕,操作系统仍会安排时间给其他线程。
创建一个线程注意:每个线程必须拥有一个进入点函数,线程从这个进入点开始运行。
线程函数可以使用任何合法的名字。
可以给线程函数传递单个参数,参数的含义由你自己定义。
线程函数必须由一个返回值,它将成为该线程的退出代码。
线程函数应该尽可能的使用函数参数和局部变量。
线程函数类似下面的样子(Object Pascal)://注意最后的stdcall,后面我会描述一些有用的东西function MyThread(info : Pointer):DWORD; stdcall;vari : integer;beginfor i := 0 to Pinfo(info)^.count-1 doForm1.Canvas.TextOut(Pinfo(info)^.x,Pinfo(info)^.y,inttostr(i));Result := 0;end;上面的的代码功能很简单,你可以在程序中直接调用,例如这样:typeTinfo = recordcount : integer;x : integer;y : integer;end;Pinfo= ^Tinfo;...procedure TForm1.Button4Click(Sender: TObject);varppi : Pinfo;beginppi :=AllocMem(sizeof(tinfo));ppi^.count := 1000000;ppi^.x := 100;ppi^.y := 400;MyThread(ppi);end;当你在一个窗口中用这样的方式调用时,你会发现在执行的过程中,你将无法在窗口上进行其他操作,因为它工作于你程序的主线程之中。
DELPHI中多线程研究
DELPHI中多线程研究作者:李若重来源:《中国新通信》2013年第04期一、进程与线程一个操作系统有多个进程在同时进行,而一个进程又会有多个线程在同时进行,每个线程都有自己的执行状态和独立的上下文结构(保存在线程控制块中)及执行栈(用户栈、系统栈),同一进程中的线程通过各种同步机制(如临界区、事件、互斥量、信号灯等)来实现对共享资源的访问。
二、Delphi中的多线程机制Delphi编译环境的核心是可以直接调用几乎所有的Windows API函数。
通常是通过过程调用一系列外部模块来实现,其最大的优点是利用面向对象的技术支持。
通过对Delphi中类实现的源代码分析,可以从中了解到类的构造过程及功能的实现,以便更有效的利用其提供的线程类完成多线程程序设计。
Delphi中多线程技术的实现是通过TThread类来封装Windows API的有关线程操作的编程接口。
TThread类继承自TOb-ject,除继承父类的成员外还定义了一些属性和方法,主要分为线程对象属性、线程对象方法、线程对象事件处理三类:(1)线程对象属性(Properties):FatalException异常处理对象FreeOnTerminate布尔量,决定线程结束时是否清除Handle线程句柄Priority线程优先级ReturnValue线程返回值Suspended布尔量,判断线程是否已挂起Terminated布尔量,判断线程是否需要结束ThreadID线程全局唯一的标记(2)线程对象方法(Methods):AfterConstruction对象创建后运行,重载自父类Create创建线程对象构造器Destroy释放线程对象析构器DoTerminate释放线程前调用用户的清除例程Execute线程执行,虚类函数,子类需重载Resume使线程重新执行Suspend挂起运行线程Synchronize线程间操作同步Terminate置线程终止标记WaitFor等待线程结束(其它继承自父类TObject对象)(3)线程对象事件处理(Events):onTerminate线程结束前调用的方法指针2.1线程的创建、运行和终止线程类调用继承自父类的构造器(con-structor Create)创建对象实例,接着调用线程管理例程的Addthread全局例程将全局线程记数值加1,随后即通过线程管理例程中的BeginThread 全局例程调用Windows API函数Createthread,以参数形式向其传入线程运行主函数Threadproc。
delphi中几种多线程操作方式
delphi中几种多线程操作方式procedure TForm1.Button1Click(Sender: TObject);vari: Integer;beginfor i := 0 to 500000 dobeginCanvas.TextOut(10, 10, IntToStr(i));end;end;上面程序运行时, 我们的窗体基本是 "死" 的, 可以在你在程序运行期间拖动窗体试试...Delphi 为我们提供了一个简单的办法(Application.ProcessMessages)来解决这个问题:procedure TForm1.Button1Click(Sender: TObject);vari: Integer;beginfor i := 0 to 500000 dobeginCanvas.TextOut(10, 10, IntToStr(i));Application.ProcessMessages;end;end;这个 Application.ProcessMessages; 一般用在比较费时的循环中, 它会检查并先处理消息队列中的其他消息.但这算不上多线程, 譬如: 运行中你拖动窗体, 循环会暂停下来...在使用多线程以前, 让我们先简单修改一下程序:function MyFun: Integer;vari: Integer;beginfor i := 0 to 500000 dobeginForm1.Canvas.Lock;Form1.Canvas.TextOut(10, 10, IntToStr(i));Form1.Canvas.Unlock;end;Result := 0;end;procedure TForm1.Button1Click(Sender: TObject);beginMyFun;end;细数上面程序的变化:1、首先这还不是多线程的, 也会让窗体假 "死" 一会;2、把执行代码写在了一个函数里, 但这个函数不属于 TForm1 的方法, 所以使用 Canvas 是必须冠以名称(Form1);3、既然是个函数, (不管是否必要)都应该有返回值;4、使用了 500001 次 Lock 和 Unlock.Canvas.Lock 好比在说: Canvas(绘图表面)正忙着呢, 其他想用 Canvas 的等会;Canvas.Unlock : 用完了, 解锁!在 Canvas 中使用 Lock 和 Unlock 是个好习惯, 在不使用多线程的情况下这无所谓, 但保不准哪天程序会扩展为多线程的; 我们现在学习多线程, 当然应该用.在 Delphi 中使用多线程有两种方法: 调用 API、使用 TThread 类; 使用 API 的代码更简单. function MyFun(p: Pointer): Integer; stdcall;vari: Integer;beginfor i := 0 to 500000 dobeginForm1.Canvas.Lock;Form1.Canvas.TextOut(10, 10, IntToStr(i));Form1.Canvas.Unlock;end;Result := 0;end;procedure TForm1.Button1Click(Sender: TObject);varID: THandle;beginCreateThread(nil, 0, @MyFun, nil, 0, ID);end;代码分析:CreateThread 一个线程后, 算上原来的主线程, 这样程序就有两个线程、是标准的多线程了; CreateThread 第三个参数是函数指针, 新线程建立后将立即执行该函数, 函数执行完毕, 系统将销毁此线程从而结束多线程的故事.CreateThread 要使用的函数是系统级别的, 不能是某个类(譬如: TForm1)的方法, 并且有严格的格式(参数、返回值)要求, 不管你暂时是不是需要都必须按格式来;因为是系统级调用, 还要缀上 stdcall, stdcall 是协调参数顺序的, 虽然这里只有一个参数没有顺序可言, 但这是使用系统函数的惯例.CreateThread 还需要一个 var 参数来接受新建线程的 ID, 尽管暂时没用, 但这也是格式; 其他参数以后再说吧.这样一个最简单的多线程程序就出来了, 咱们再用 TThread 类实现一次typeTMyThread = class(TThread)protectedprocedure Execute; override;end;procedure TMyThread.Execute;vari: Integer;beginFreeOnTerminate := True; {这可以让线程执行完毕后随即释放}for i := 0 to 500000 dobeginForm1.Canvas.Lock;Form1.Canvas.TextOut(10, 10, IntToStr(i));Form1.Canvas.Unlock;end;end;procedure TForm1.Button1Click(Sender: TObject);beginTMyThread.Create(False);end;TThread 类有一个抽象方法(Execute), 因而是个抽象类, 抽象类只能继承使用, 上面是继承为TMyThread.继承 TThread 主要就是实现抽象方法 Execute(把我们的代码写在里面), 等我们的 TMyThread 实例化后, 首先就会执行 Execute 方法中的代码.按常规我们一般这样去实例化:procedure TForm1.Button1Click(Sender: TObject);varMyThread: TMyThread;beginMyThread := TMyThread.Create(False);end;因为 MyThread 变量在这里毫无用处(并且编译器还有提示), 所以不如直接写做TMyThread.Create(False);我们还可以轻松解决一个问题, 如果: TMyThread.Create(True) ?这样线程建立后就不会立即调用 Execute, 可以在需要的时候再用 Resume 方法执行线程, 譬如: procedure TForm1.Button1Click(Sender: TObject);varMyThread: TMyThread;beginMyThread := TMyThread.Create(True);MyThread.Resume;end;//可简化为:procedure TForm1.Button1Click(Sender: TObject);beginwith TMyThread.Create(True) do Resume;end;一、入门㈠、function CreateThread(lpThreadAttributes: Pointer; {安全设置}dwStackSize: DWORD; {堆栈大小}lpStartAddress: TFNThreadStartRoutine; {入口函数}lpParameter: Pointer; {函数参数}dwCreationFlags: DWORD; {启动选项}var lpThreadId: DWORD {输出线程 ID }): THandle; stdcall; {返回线程句柄}在 Windows 上建立一个线程, 离不开 CreateThread 函数;TThread.Create 就是先调用了 BeginThread (Delphi 自定义的), BeginThread 又调用的CreateThread.既然有建立, 就该有释放, CreateThread 对应的释放函数是: ExitThread, 譬如下面代码: procedure TForm1.Button1Click(Sender: TObject);beginExitThread(0); {此句即可退出当前程序, 但不建议这样使用}end;代码注释:当前程序是一个进程, 进程只是一个工作环境, 线程是工作者;每个进程都会有一个启动线程(或叫主线程), 也就是说: 我们之前大量的编码都是写给这个主线程的;上面的 ExitThread(0); 就是退出这个主线程;系统不允许一个没有线程的进程存在, 所以程序就退出了.另外: ExitThread 函数的参数是一个退出码, 这个退出码是给之后的其他函数用的, 这里随便给个无符号整数即可.或许你会说: 这个 ExitThread 挺好用的; 其实不管是用 API 还是用 TThread 类写多线程, 我们很少用到它; 因为:1、假如直接使用 API 的 CreateThread, 它执行完入口函数后会自动退出, 无需 ExitThread;2、用 TThread 类建立的线程又绝不能使用 ExitThread 退出; 因为使用 TThread 建立线程时会同时分配更多资源(譬如你自定义的成员、还有它的祖先类(TObject)分配的资源等等), 如果用ExitThread 给草草退出了, 这些资源将得不到释放而导致内存泄露. 尽管 Delphi 提供了EndThread(其内部调用 ExitThread), 这也不需要我们手动操作(假如非要手动操作也是件很麻烦的事情, 因为很多时候你不知道线程是什么时候执行完毕的).除了 CreateThread, 还有一个 CreateRemoteThread, 可在其他进程中建立线程, 这不应该是现在学习的重点;现在先集中精力把 CreateThread 的参数搞彻底.倒着来吧, 先谈谈 CreateThread 将要返回的 "线程句柄"."句柄" 类似指针, 但通过指针可读写对象, 通过句柄只是使用对象;有句柄的对象一般都是系统级别的对象(或叫内核对象); 之所以给我们的是句柄而不是指针, 目的只有一个: "安全";貌似通过句柄能做很多事情, 但一般把句柄提交到某个函数(一般是系统函数)后, 我们也就到此为止很难了解更多了; 事实上是系统并不相信我们.不管是指针还是句柄, 都不过是内存中的一小块数据(一般用结构描述), 微软并没有公开句柄的结构细节, 猜一下它应该包括: 真实的指针地址、访问权限设置、引用计数等等.既然 CreateThread 可以返回一个句柄, 说明线程属于 "内核对象".实际上不管线程属于哪个进程, 它们在系统的怀抱中是平等的; 在优先级(后面详谈)相同的情况下,系统会在相同的时间间隔内来运行一下每个线程, 不过这个间隔很小很小, 以至于让我们误以为程序是在不间断地运行.这时你应该有一个疑问: 系统在去执行其他线程的时候, 是怎么记住前一个线程的数据状态的?有这样一个结构 TContext, 它基本上是一个 CPU 寄存器的集合, 线程是数据就是通过这个结构切换的, 我们也可以通过 GetThreadContext 函数读取寄存器看看.附上这个结构 TContext(或叫: CONTEXT、_CONTEXT) 的定义:PContext = ^TContext;_CONTEXT = recordContextFlags: DWORD;Dr0: DWORD;Dr1: DWORD;Dr2: DWORD;Dr3: DWORD;Dr6: DWORD;Dr7: DWORD;FloatSave: TFloatingSaveArea;SegGs: DWORD;SegFs: DWORD;SegEs: DWORD;SegDs: DWORD;Edi: DWORD;Esi: DWORD;Ebx: DWORD;Edx: DWORD;Ecx: DWORD;Eax: DWORD;Ebp: DWORD;Eip: DWORD;SegCs: DWORD;EFlags: DWORD;Esp: DWORD;SegSs: DWORD;end;CreateThread 的最后一个参数是 "线程的 ID";既然可以返回句柄, 为什么还要输出这个 ID? 现在我知道的是:1、线程的 ID 是唯一的; 而句柄可能不只一个, 譬如可以用 GetCurrentThread 获取一个伪句柄、可以用 DuplicateHandle 复制一个句柄等等.2、ID 比句柄更轻便.在主线程中 GetCurrentThreadId、MainThreadID、MainInstance 获取的都是主线程的 ID.㈡、启动选项function CreateThread(lpThreadAttributes: Pointer;dwStackSize: DWORD;lpStartAddress: TFNThreadStartRoutine;lpParameter: Pointer;dwCreationFlags: DWORD; {启动选项}var lpThreadId: DWORD): THandle; stdcall;CreateThread 的倒数第二个参数 dwCreationFlags(启动选项) 有两个可选值:0: 线程建立后立即执行入口函数;CREATE_SUSPENDED: 线程建立后会挂起等待.可用 ResumeThread 函数是恢复线程的运行; 可用 SuspendThread 再次挂起线程.这两个函数的参数都是线程句柄, 返回值是执行前的挂起计数.什么是挂起计数?SuspendThread 会给这个数 +1; ResumeThread 会给这个数 -1; 但这个数最小是 0.当这个数 = 0 时, 线程会运行; > 0 时会挂起.如果被 SuspendThread 多次, 同样需要 ResumeThread 多次才能恢复线程的运行.在下面的例子中, 有新线程不断给一个全局变量赋随机值;同时窗体上的 Timer 控件每隔 1/10 秒就把这个变量写在窗体标题;在这个过程中演示了 ResumeThread、SuspendThread 两个函数.//上面图片中演示的代码。
delphi中的线程类详解
delphi中的线程类Delphi中有一个线程类TThread是用来实现多线程编程的,这个绝大多数Delphi书藉都有说到,但基本上都是对TThread类的几个成员作一简单介绍,再说明一下Execute的实现和Synchronize 的用法就完了。
然而这并不是多线程编程的全部,我写此文的目的在于对此作一个补充。
线程本质上是进程中一段并发运行的代码。
一个进程至少有一个线程,即所谓的主线程。
同时还可以有多个子线程。
当一个进程中用到超过一个线程时,就是所谓的“多线程”。
那么这个所谓的“一段代码”是如何定义的呢?其实就是一个函数或过程(对Delphi而言)。
如果用Windows API来创建线程的话,是通过一个叫做CreateThread的API函数来实现的,它的定义为:HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,DWORD dwStackSize,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter,DWORD dwCreationFlags,LPDWORD lpThreadId);其各参数如它们的名称所说,分别是:线程属性(用于在NT下进行线程的安全属性设置,在9X下无效),堆栈大小,起始地址,参数,创建标志(用于设置线程创建时的状态),线程ID,最后返回线程Handle。
其中的起始地址就是线程函数的入口,直至线程函数结束,线程也就结束了。
整个线程的执行过程如下图所示:因为CreateThread参数很多,而且是Windows的API,所以在C Runtime Library里提供了一个通用的线程函数(理论上可以在任何支持线程的OS中使用):unsigned long _beginthread(void (_USERENTRY *__start)(void *), unsigned __stksize, void *__arg);Delphi也提供了一个相同功能的类似函数:function BeginThread(SecurityAttributes: Pointer; StackSize: LongWord; ThreadFunc: TThreadFunc; Parameter: Pointer; CreationFlags: LongWord; var ThreadId: LongWord): Integer;这三个函数的功能是基本相同的,它们都是将线程函数中的代码放到一个独立的线程中执行。
多线程的使用(Delphi)[转]
pt:TPainterThread;
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
try
pixels[x,y]:=clblue;
finally
unlock;
end;
end;
until Terminated;
end;
end.
form调用TThread方法举例:
在form上建立两个button组件.
public
{ Public declarations }
end;
var
AnimWindow: TAnimWindow;
run: Boolean;
implementation
{$R *.dfm}
procedure TAnimThread.Execute;
举例:
建立TThread类代码
unit Unit2;
interface
uses
Classes;
type
TPainterThread = class(TThread)
private
{ Private declarations }
protected
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
delphi多线程编程
delphi多线程编程delphi多线程编程━━━━━━━━━━━━━━━━━━━━━━━━━━本章描述Windows 95与Windows NT的基于线程的多任务设计。
15.1 线程我们知道,Windows 95支持两种形式的多任务。
第一种类型是基于进程的机制,这也是Windows从一开始就支持的多处理类型。
进程本质上是指正在执行着的程序,在基于进程的多任务环境下,两个至多个进程可以并发地执行。
第二种类型是基于线程(thread)的机制,基于线程的多任务对于多数Windows用户和程序员而言是一个崭新的概念,因为Windows 95以前的Windows版本不能支持线程的概念(Windows NT除外)。
线程是指进程中的一个执行流。
多个线程可以并发地运行于同一个进程中。
在Windows 95/98/NT中,每一个进程拥有至少一个线程,允许同时执行两个或多个线程。
并由我们的程序控制它们。
基于线程的多任务允许同一程序的不同部分(线程)并发地执行。
这样,程序员就能够写出非常高效的程序,因为程序员能够定义执行线程并管理程序的执行方式,能够完全地控制程序片段的执行。
例如,可以在一个程序中指定一个线程执行文件排序工作,指定另外一个线程负责收集来自某个远程资源的信息,指定又一线程完成用户输入的工作。
因为处于多线程多任务环境,每一线程都能够并发地执行,这样就能充分地利用CPU时间。
通常,多线程处理使程序运行速度减慢,除非我们有多线程CPU,以及可以在处理器中分离线程的操作系统。
基于线程的多任务使得同步功能特征显得更为重要。
既然多个线程(及进程)可以并发执行,那么必须适当地协调线程间的执行顺序以使其能同步访问共享资源与内存,从而使程序编写起来显得更加复杂。
Windows 95增加了一个完整的子系统以支持同步机制,其中的一些关键特征将在本章后进一步地讨论。
15.2 线程类所有进程至少都拥有一个执行线程,为了讨论方便,我们称该执行线程为主线程。
Delphi中多线程研究分析详解
Delphi中多线程分析详解————————————————————————————————作者:————————————————————————————————日期:Delphi中多线程分析详解时间:2011-9-3 15:35:57 点击:1530核心提示:0. 前言多线程是多任务操作系统下一个重要的组成部分,它能够提高应用程序的效率,然而,我们想利用好多线程,必须要了解很多的东西,比如操作系统的原理,堆栈概念和使用方法。
然而,使用不当,将会造成无尽的痛...0. 前言多线程是多任务操作系统下一个重要的组成部分,它能够提高应用程序的效率,然而,我们想利用好多线程,必须要了解很多的东西,比如操作系统的原理,堆栈概念和使用方法。
然而,使用不当,将会造成无尽的痛苦。
曾经刚刚接触的时候,我也为之恐惧,迷惑了好久。
在无数次的失败和查找资料解决问题之后,稍有感触,故写下此文,总结一下自己,同时,也给后学者一点启示,希望让他们少走弯路。
1. 基础知识。
线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。
一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
由于线程之间的相互制约,致使线程在运行中呈现出间断性。
线程也有就绪、阻塞和运行三种基本状态。
线程的生死。
在windows中,我们可以通过调用API CreateThread/CreateRemoteThread创建一个线程(其实,在Windows内部,CreateThread最终是调用了CreateRemoteThread创建线程)。
当线程函数执行退出时,可以说这个线程已经完成了它的使命。
调用ExitThread可以结束一个线程,同时调用CloseHandle来释放Windows分配给它的句柄资源。
GetExitCodeThread可以用来检测线程是否已经退出。
Delphi多线程及消息发送传递结构体参数
Delphi多线程及消息发送传递结构体参数1、Unit2:unit Unit2;interfaceuses windows,classes,NMICMP,SysUtils,StdCtrls,messages;const WM_MY_PING = WM_USER +1024;type//要传递的消息记录.TPingMsg = recordmsg : array[0..1023] of char;id : integer;Handled : boolean;msg2 : string; //建议如果需要动态管理,⽐如采⽤List,采⽤字符数组的⽅式会⽐较好,//因为在动态使⽤结构时,如过没有处理好,采⽤string就可能会造成内存泄露.//当然在这⾥例⼦中没关系.end;pPingMsg = ^TPingMsg;//定义结构体指针.OnPinging = procedure(Context: integer;Msg : string) of object;ThreadEnd = procedure(Context: integer;Msg:string) of object;TMyPingThread = class(TThread)privateFPingEvent : OnPinging;FEndEvent : ThreadEnd;FMsg : string;FSequenceID : integer;FWinHandl : Hwnd;procedure OnPing(Sender: TObject; Host: String; Size, Time: Integer);procedure HandlingEnd;procedure HandlingPing;protectedprocedure Execute;override;procedure DoTerminate;override;public//采⽤函数指针的⽅式,因为传递过来如果是UI控件类的⽅法,该⽅法需要访问UI元素,则需要做同步处理,//否则可能会导致错误.constructor Create(WinHandl : Hwnd; SequenceID : integer;OutPut: OnPinging;EndEvent: ThreadEnd);overload;end;implementation{ TMyPingThread }constructor TMyPingThread.Create(WinHandl : Hwnd;SequenceID : integer;OutPut: OnPinging; EndEvent: ThreadEnd);beginself.FPingEvent := OutPut;self.FEndEvent := EndEvent;FSequenceID := SequenceID;FWinHandl := WinHandl;inherited Create(true);end;procedure TMyPingThread.DoTerminate;begininherited;Synchronize(HandlingEnd);end;procedure TMyPingThread.HandlingEnd();beginif Assigned(self.FEndEvent) thenself.FEndEvent(FSequenceID,FMsg);end;procedure TMyPingThread.HandlingPing();beginif assigned(self.FPingEvent) thenFPingEvent(FSequenceID,FMsg);end;procedure TMyPingThread.Execute;varPingObj : TNMPing;beginself.FreeOnTerminate := true;PingObj := TNMPing.Create(nil);PingObj.OnPing := OnPing;tryPingObj.Pings := 30;PingObj.Host := '';PingObj.Ping;finallyPingObj.Free;end;end;procedure TMyPingThread.OnPing(Sender: TObject; Host: String; Size,Time: Integer);varpMsg : pPingMsg;Msg : TPingMsg;begin//不能直接定义结构体,因为是局部变量,如果是PostMessage,不会等待,会释放的.//但如果采⽤如下的new⽅式,程序不会主动释放内存,需要配合Dispose⽅法⽤.new(pmsg);//这种情况下,消息接收⽅不⼀定能获取到正确的值.FMsg := host+':'+ inttostr(size)+':'+inttostr(Time);strcopy(@(pmsg.msg),pchar(FMsg));pmsg.id := self.FSequenceID;pmsg.Handled := false;pmsg.msg2 := FMsg+'xxx';//注意,这⾥增加字符,并不能增加sizeof(pmsg^)Msg.msg2 := FMsg+'xxxx';//注意,这⾥增加字符,并不能增加sizeof(Msg)strcopy(@(Msg.msg),pchar(FMsg));//postmessage(FWinHandl,WM_MY_PING, self.FSequenceID,LPARAM(@Msg));//因此我觉得采⽤SendMessage⽐较好,这样内存的释放可以在这⾥进⾏,不会造成内存泄露. Sendmessage(FWinHandl,WM_MY_PING, self.FSequenceID,LPARAM(@Msg));//这种⽅法是让线程等待消息处理,实际上等效于SendMessage⽅法调⽤.{while (pmsg.Handled=false) dobeginsleep(10);end;}//采⽤等待⽅法则在这⾥释放空间。
DelphiXE的多线程
DelphiXE的多线程先看一个非多线程的例子, 代码执行时不能进行其它操作(譬如拖动窗体):--------------------------------------------------------------------------------{自定义方法: 在窗体上绘制...}procedure MyMethod;vari: Integer;beginfor i := 0 to 500000 dobeginForm1.Canvas.Lock;Form1.Canvas.TextOut(10, 10, IntToStr(i));Form1.Canvas.Unlock;end;end;{调用上面的自定义方法}procedure TForm1.Button1Click(Sender: TObject);beginMyMethod;end;--------------------------------------------------------------------------------修改为多线程(只修改一行代码):--------------------------------------------------------------------------------procedure MyMethod;vari: Integer;beginfor i := 0 to 500000 dobeginForm1.Canvas.Lock;Form1.Canvas.TextOut(10, 10, IntToStr(i));Form1.Canvas.Unlock;end;end;procedure TForm1.Button1Click(Sender: TObject);beginTThread.CreateAnonymousThread(MyMethod).Start; //end;--------------------------------------------------------------------------------代码分析:1、TThread 现在增加了许多class 方法(直接通过类名调用的方法), TThread.CreateAnonymousThread() 就是比较有用的一个.2、顾名思义, CreateAnonymousThread 是建立匿名线程对象, 它的参数是我们需要在线程中执行的方法.3、但 CreateAnonymousThread 建立线程后是挂起的, 需要手动运行它; 后面的 Start 方法就是用来唤醒线程的.4、(以前)唤醒线程还可以使用 Resume 方法或 Suspended 属性(Suspended := False;); 但它们即将被废弃了, 现在应使用 Start 来启动线程.--------------------------------------------------------------------------------CreateAnonymousThread 的参数类型TProc 是匿名方法(reference), 所以代码可以简写为:--------------------------------------------------------------------------------procedure TForm1.Button1Click(Sender: TObject);beginTThread.CreateAnonymousThread( //直接写入方法体procedurevari: Integer;beginfor i := 0 to 500000 dobeginCanvas.Lock;Canvas.TextOut(10, 10, IntToStr(i));Canvas.Unlock;end;end //此处无分号).Start;end;--------------------------------------------------------------------------------延时执行:--------------------------------------------------------------------------------varmyThread: TThread;procedure TForm1.FormCreate(Sender: TObject);beginmyThread := TThread.CreateAnonymousThread( procedurevari: Integer;beginfor i := 0 to 500000 dobeginCanvas.Lock;Canvas.TextOut(10, 10, IntToStr(i));Canvas.Unlock;end;end);end;procedure TForm1.Button1Click(Sender: TObject); beginmyThread.Start;end;。
Delphi Thread 线程代码分析
因为CreateThread参数很多,而且是Windows的API,所以在C Runtime Library里提供了一个通用的线程函数(理论上
可以在任何支持线程的OS中使用):
unsigned long _beginthread(void (_USERENTRY *__start)(void *), unsigned __stksize, void *__arg);
CreationFlags: LongWord;
var ThreadId: LongWord
): Integer;
这三个函数的功能是基本相同的,它们都是将线程函数中的代码放到一个独立的线程中执行。线程函数与一般函数的
最大不同在于,线程函数一启动,这三个线程启动函数就返回了,主线程继续向下执行,而线程函数在一个独立的线
虽然说用API或RTL(Runtime Library)已经可以很方便地进行多线程编程了,但是还是需要进行较多的细节处理,为此
Delphi在Classes单元中对线程作了一个较好的封装,这就是VCL的线程类:TThread
使用这个类也很简单,大多数的Delphi书籍都有说,基本用法是:先从TThread派生一个自己的线程类(因为TThread
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
其各参数如它们的名称所说,分别是:线程属性(用于在NT下进行线程的安全属性设置,在9X下无效),堆栈大小,
起始地址,参数,创建标志(用于设置线程创建时的状态),线程ID,最后返回线程Handle。其中的起始地址就是线
FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID);
Delphi多线程学习(5):互斥量Mutex
Delphi多线程学习(5):互斥量Mutex 互斥量是系统内核对象,谁拥有就谁执⾏。
它与临界区⼯作很类似。
不同处在于:1、互斥量可以跨进程边界同步线程。
2、可以给互斥量取个名字,通过引⽤互斥量的名字来使⽤⼀个已知的互斥量对象。
使⽤互斥量之类的对象需要反复调⽤系统内核,期间需要进⾏进程上下⽂转换和控制级别转换,⼤概需要耗费400到600个时间周期。
⼜是图书馆的⽐喻,现在是搞⼀个锁,把钥匙(互斥量句柄)交给管理员(操作系统),每⼀个⼈(线程)想要借书的时候,都要向管理员拿钥匙。
当有⼈在使⽤的时候,另⼀⼈必须等待,等到钥匙有空的时候(互斥量进⼊信号状态),才能拿到钥匙(拥有了句柄)办理借书业务(此时互斥量进⼊⾮信号状态直到办完业务)。
使⽤互斥量的步骤:1、声明⼀个全局的互斥量句柄变量(var hMutex: THandle;);2、创建互斥量:CreateMutex(lpMutexAttributes: PSecurityAttributes;bInitialOwner: BOOL;lpName: PWideChar ): THandle;(lpMutexAttributes参数:指向TSecurityAttributes的指针,安全属性,⼀般⽤缺省安全属性nil;bInitialOwer参数:表⽰创建的互斥量线程是否是互斥量的属主,如果该参数为False互斥量就没属主,⼀般来讲应设为False,否则如果设为True的话,要当主线程结束其他线程才成为它的属主才能运⾏;lpName参数:是互斥量的名字,若打算取名的话,则传⼊nil。
)3、⽤等待函数控制线程进⼊同步代码块:WaitForSingleObject(hHandle:THandel;dwMilliseconds:DWORD):DWORD;(hHandel参数:是对象的句柄,不同对象信号状态不同,对于互斥量⽽⾔当没有线程占有时,互斥量就时⼊信号状态;dwMilliseconds参数:可以是⼀个时间段,可以是0表⽰仅检查对象状态后⽴即返回,可以是INFINITE值表⽰函数⼀直等待直到对象进⼊信号状态;返回值常量如下:WAIT_ABANDONED指定的对象是互斥量对象,拥有这个互斥量对象的线程在没有释放互斥量之前就已经终⽌,称作废弃互斥量,此时该互斥量归调⽤线程所拥有,并把这个互斥量设为⾮信号状态;WAIT_OBJECT_0指定对象的进⼊信号状态;WAIT_TIMEOUT等待时间已过,对象状态依然是⽆信号状态)4、执⾏线程运⾏代码。
Delphi多线程实例解析
[摘要] 我们都知道当前的Windows操作系统是一个“多线程”操作系统。
那么什么是线程呢?线程就是进程中的一个实体,它和进程一样能够独立的执行控制,由操作系统负责调度,其区别就在于线程没有独立的存储空间,而是与同属于一个进程的其他线程共享一个存储空间,这使得多线程之间的通信较进程简单,并且多线程的执行都是并发而且是相互独立的。
[关键字] delphi windows操作系统【eNet硅谷动力专稿】我们都知道当前的Windows操作系统是一个“多线程”操作系统。
那么什么是线程呢?线程就是进程中的一个实体,它和进程一样能够独立的执行控制,由操作系统负责调度,其区别就在于线程没有独立的存储空间,而是与同属于一个进程的其他线程共享一个存储空间,这使得多线程之间的通信较进程简单,并且多线程的执行都是并发而且是相互独立的。
为了运行所有这些线程,操作系统为每个独立线程安排一些CPU 时间,操作系统以轮转方式向线程提供时间片,这就给人一种假象,好象这些线程都在同时运行。
CreatThread函数是用于创建一个线程,CreatThread函数原形及参数说明如下:HANDLE CreatThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,DWORD dwStackSize,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter,DWORD dwCreationFlags,LPDWORD lpThreadld),参数说明:pThreadAttributes 如果为NULL,该线程使用默认安全属性。
如果希望所有子进程能够继承该线程对象的句柄,必须将他的bInheritHand成员初始化为True。
dwStackSize 设定线程堆栈的地址空间。
如果非0,函数将所有的存储器保留并分配给线的程堆栈。
lpStartAddress 线程函数的地址。
多线程在Delphi中的使用
一
library in Delphi.
期
Key words : Multithreading; Progression; Delphi; API Function
)
!"# MO D E R N C OMP U T E R 2007.1
这个 TThread 对象封装了 WindowsAPI 和 System 单元中有关线程运用的多个函数和例程, 利用操作系 统 分 时 段 给 各 线 程 的 方 式 控 制 各 个 线 程 的 “休 眠 ”与 “唤醒”, 以达 到线程工作的同步, 当被“唤醒”后就调 用 TThread 对象的 Synchronize 过程通知主线程, 让主 线程去访问 VCL, 使得在一个应用程序中同时访问多 个 VCL 库成为可能。在使用上它与 Delphi 中大多数 对象不同的是, TThread 类是 一个带有虚拟抽 象 对 象 方法的类, 我们不能直接创建 TThread 对象的实例, 而 必须先声明一个以 TThread 对象为基类的类, 再用这个 派生类创建实例和操纵线程具体的类属性和方法。
在程序中加入 Tthread 的方法是, 首先选择 File| New 选项, 选中 Thread Object 后, 会出现一个对话 框 , 要 求输入一个 Tthread 类的名字, 例如, 可以输入 Test, 这样 Delphi 就会建立一个 Test 的类。
delphi 串口与多线程
unit Unit1;interfaceusesWindows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ComCtrls, TeEngine, Series, ExtCtrls, TeeProcs, Chart, OleCtrls, MSCommLib_TLB;typeTForm1 = class(TForm)tbshtSignalChart: TPageControl;tbshtDebug: TTabSheet; //串口调试rchdtSerialIn: TRichEdit; //串口发送的数据cmbbxSerialIn: TComboBox; //串口号Label7: TLabel;btRecStart: TButton; //开始接受btRecStop: TButton; //停止cmbbxBaudIn: TComboBox;Label8: TLabel;Label9: TLabel;Label10: TLabel;Label11: TLabel;btRecSave: TButton; //保存cmbbxTypeIn: TComboBox;cmbbxSerialOut: TComboBox; /串口端号Label13: TLabel;cmbbxBaudOut: TComboBox; //波特率rchdtSerialOut: TRichEdit; //输入发送btSendOut: TButton;dtCode1: TEdit;lbCode1: TLabel;lbCode2: TLabel;dtCode3: TEdit;cmbbxTypeOut: TComboBox; //发送数据的格式Label16: TLabel;Label51: TLabel;tmrSerialIn: TTimer;MSCommOut: TMSComm;MSCommIn: TMSComm;dtCode2: TEdit;dtCode4: TEdit;lbCode3: TLabel;lbCode4: TLabel;Label17: TLabel; Label18: TLabel; Label19: TLabel; Label20: TLabel; Label21: TLabel; Label22: TLabel; Label23: TLabel; Label24: TLabel; Label25: TLabel; Label26: TLabel; lbShowAd0: TLabel; lbShowAd1: TLabel; lbShowAd4: TLabel; lbShowAd5: TLabel; lbShowAd2: TLabel; lbShowGps5: TLabel; lbShowGps1: TLabel; lbShowAd7: TLabel; lbShowAd6: TLabel; lbShowAd3: TLabel; Label37: TLabel; lbShowGps6: TLabel; lbShowGps4: TLabel; Label53: TLabel; Label54: TLabel; Label55: TLabel; lbShowGps0: TLabel; btSaveTrace: TButton; btLoadTrace: TButton; btSaveData: TButton; Label1: TLabel; lbShowModel: TLabel; Label3: TLabel; Label4: TLabel; Label5: TLabel; Label6: TLabel; Label14: TLabel; Label15: TLabel; Label39: TLabel; Label40: TLabel; lbShowPara0: TLabel; lbShowPara1: TLabel; lbShowPara2: TLabel;lbShowPara4: TLabel;lbShowPara5: TLabel;lbShowPara6: TLabel;lbShowPara7: TLabel;Label49: TLabel;lbShowGps2: TLabel;Label57: TLabel;lbShowGps3: TLabel;Label59: TLabel;cmbbxModel: TComboBox; btSetModel: TButton;cmbbxParaNo: TComboBox;Label38: TLabel;Label60: TLabel;Label61: TLabel;dtParaValue: TEdit;btADInit: TButton;btSetPara: TButton;Label62: TLabel;rdgrpDataCode: TRadioGroup; dlgSaveData: TSaveDialog; dlgLoadTrace: TOpenDialog; dlgSaveTrace: TSaveDialog;rchdtTmp: TRichEdit;Label2: TLabel;TabSheet1: TTabSheet;chrtAcc: TChart;chrtGyro: TChart;srsGyroY: TFastLineSeries;srsGyroX: TFastLineSeries;srsGyroZ: TFastLineSeries;srsAcc1: TFastLineSeries;srsAcc2: TFastLineSeries;chrtHeight: TChart;srsHeight: TFastLineSeries;Label12: TLabel;btCharClear: TButton;tbshtSignal: TTabSheet;procedure FormCreate(Sender: TObject); procedure btRecStartClick(Sender: TObject); procedure btRecStopClick(Sender: TObject); procedure btRecSaveClick(Sender: TObject); procedure btSendOutClick(Sender: TObject);procedure tmrSerialInTimer(Sender: TObject);procedure btSetModelClick(Sender: TObject);procedure btSetParaClick(Sender: TObject);procedure FormDestroy(Sender: TObject);procedure btSaveTraceClick(Sender: TObject);procedure btLoadTraceClick(Sender: TObject);procedure btADInitClick(Sender: TObject);procedure btSaveDataClick(Sender: TObject);procedure btCharClearClick(Sender: TObject);procedure dtCode1Change(Sender: TObject);procedure dtCode2Change(Sender: TObject);procedure dtCode3Change(Sender: TObject);procedure dtCode4Change(Sender: TObject);private{ Private declarations }public{ Public declarations }InitADDataFlag:Boolean;end;/////////////////////////////////////////////////////////////////{串口接受线程}TSerialInThread =class(TThread)privateSerialInput:Variant; //保存该线程从串口获取得到的数据矩阵不同类型的值赋Seriallength : integer; //该线程需要处理的数据的个数SerialStr:String; //用于保存处理后数据的字符串变量protectedprocedure Execute; override; //需要重载procedure GetData; //从串口获取数据的过程procedure TransToStr; //转换为适合显示的字符串的过程procedure PostData ; //显示在用户界面的过程end;/////////////////////////////////////////////////////////////////{串口发送线程}TSerialOutThread =class(TThread)privateDataOrCode:Boolean; //是否要求输出指令,False表明输出数据SerialOutput:String; //发送数据时的数据矩阵Seriallength : integer; //发送数据时,数据长度tmpVar:Variant; //发送指令时的数据矩阵CheckOk:Boolean; //用户确定的数据或是指令是否有错误protectedprocedure Execute; override; //需要重载procedure GetData; //从用户界面获取需要输出的数据或是指令代码procedure PackData; //根据协议打包数据procedure PackCode; //根据协议打包指令procedure PostData; //发送给串口控件,实现数据的发送end;varForm1: TForm1;implementation{$R *.dfm}constGpsDataCountMax : integer = 20; //GPS数据包中数据的个数ParaDataCountMax: integer = 9; //参数数据包中数据的个数ADDataCountMax:integer =16; //传感器数据包中数据的个数var{实现线程互斥的句柄。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Delphi中多线程分析详解时间:2011-9-3 15:35:57 点击:1530核心提示:0. 前言多线程是多任务操作系统下一个重要的组成部分,它能够提高应用程序的效率,然而,我们想利用好多线程,必须要了解很多的东西,比如操作系统的原理,堆栈概念和使用方法。
然而,使用不当,将会造成无尽的痛...0. 前言多线程是多任务操作系统下一个重要的组成部分,它能够提高应用程序的效率,然而,我们想利用好多线程,必须要了解很多的东西,比如操作系统的原理,堆栈概念和使用方法。
然而,使用不当,将会造成无尽的痛苦。
曾经刚刚接触的时候,我也为之恐惧,迷惑了好久。
在无数次的失败和查找资料解决问题之后,稍有感触,故写下此文,总结一下自己,同时,也给后学者一点启示,希望让他们少走弯路。
1. 基础知识。
线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。
一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
由于线程之间的相互制约,致使线程在运行中呈现出间断性。
线程也有就绪、阻塞和运行三种基本状态。
线程的生死。
在windows中,我们可以通过调用API CreateThread/CreateRemoteThread创建一个线程(其实,在Windows内部,CreateThread最终是调用了CreateRemoteThread创建线程)。
当线程函数执行退出时,可以说这个线程已经完成了它的使命。
调用ExitThread可以结束一个线程,同时调用CloseHandle来释放Windows分配给它的句柄资源。
GetExitCodeThread可以用来检测线程是否已经退出。
HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD,线程的属性DWORD dwStackSize, // initial stack size,线程堆栈的大小LPTHREAD_START_ROUTINE lpStartAddress, // thread function,线程函数LPVOID lpParameter, // thread argument,参数DWORD dwCreationFlags, // creation option,创建时的标志LPDWORD lpThreadId // thread identifier,线程的ID);线程的控制。
线程的有三种状态:就绪,阻塞,运行。
当我们在CreateThread的时候,第5个参数为CREATE_SUSPENDED标志时,线程创建后就处于挂起,即阻塞状态,否则,线程就会调用线程函数立即执行。
ResumeThread可以让线程阻塞的线程继续运行,SuspendThread可以让线程挂起。
(具体用法参考MSDN)2. 线程同步不同线程间公用同一个资源的时候,就需要进行线程同步。
为何要同步?要回答好这个问题我们要从栈说起。
这里说的栈,和数据结构中的堆栈是不一样的。
(穿插一个小的知识:堆和栈的区别。
以前看过一个帖子,里面有个很精辟的回复,说明了堆和栈的区别:“堆就像自己在家里做饭,想做什么就做什么,但是,最后的锅碗等还需要自己去收拾;而栈就像是去餐馆吃饭,只要你点好菜,餐馆就给你提供,吃完之后锅碗什么的都不需要自己管。
”,这说明堆和栈的区别以及如何使用它们:堆,可以自己完全控制,用完之后需要自己清理,处理不好就会造成内存泄漏;栈,由操作系统分配,不需要进行管理,不用担心内存泄漏)。
简单的说,栈就是一块内存区域,它是从大到小增长的,它遵循后进先出的原则(FILO,First In Last Out)。
通常,CPU的EBP和ESP是用作栈的,EBP是栈的基地址,EBP是当前栈顶的位置(栈顶永远是小于等于栈底的)。
栈的主要作用就是保存现场,函数参数传递。
对于栈的操作汇编中有两条指令:PUSH和POP,分别用于数据入栈和出栈。
这两条指令可以影响ESP的值,当然你也可以直接使用SUB ESP XXX、ADD ESP XXX这种方式来更改栈顶的位置。
我们来看看函数的调用过程(这里不考虑调用惯例,仅仅是个示意):PUSH EBP // 将当前栈底的位置压入栈SUB ESP, XXXX // 为函数开辟栈,XXXX为栈的大小PUSH 参数// 参数入栈CALL SomeAddress // 调用函数ADD ESP, XXXX // 释放为函数开辟的栈(这里就解释了为什么我们不需要去管在栈上分配的内存)POP EBP // 恢复EBP的位置600)this.width = 600;">每个线程有自己的栈,在CreateThread的时候,第二个参数就是用来指定线程的栈的大小,传入0时,系统会自动分配栈的大小。
现在看多线程使用共享资源(可以是公共变量,也可以是公共代码等)时的情况。
如图,A和B共享一个资源S,A首先获取到了资源S,得到S的状态S1,线程A开始运行,当A运行了一段时间后,A的线程时间片用完,于是A被操作系统挂起,在挂起的时候系统会将A的运行状态记录到A的堆栈中,以便下次唤醒A是能正常运行。
这是共享资源S的状态S1也被保存到了A的堆栈中。
接下来,线程B获得了运行权利,开始运行,它也得到了S的状态S2,B开始运行,并且改变了S 的状态,假设改变成S3。
B运行结束后。
A重新被唤醒了,A从栈中取出S的状态S1继续运行,而这时,S的实际状态已经变成S3,而A并不知道,于是,A运行的结果就错误了。
也许有些混乱,我们举个更简单的例子:线程A和B共用一个公共变量S(假设为int,初始值为1)。
我们再来看这个过程:A开始运行获取S值1,A运行-> A被挂起-> 此时线程A中S的值1被保存到A的栈中–> B 开始运行,并且修改S的数值为100 –> A被唤醒–> A获取S的值1 -> A 将运行的结果保存到S。
我们看这个过程中,S的值混乱了。
所以,我们必须对共享资源进行保护。
600)this.width = 600;">在进行了线程同步时,当A获取到S后,其它任何线程将不能获取和修改S,这样就保证S不再混乱。
总结一下,线程实现了进程并发运行的效果,线程同步是为了解决线程并发的“冲突”问题(共享资源读写)。
(小知识:调用栈在程序调试中有重要的作用,当程序发生异常时,我们可以调出它来追查原因。
VC中按下Alt + 7可以调出调用栈窗口,Delphi中按下Ctrl + Alt + S可以调出调用栈)如何同步?在Windows系统中,我们可以使用互斥量,信号量,事件,重要区段等方式进行线程同步。
重要区段仅仅可以用于同一个进程中的不同线程之间的同步,它运行与用户态,其效率是最高的。
其余的运行与内核态,可以用于不同进程间(需要在用户态和内核态进行切换)。
信号量可以允许多个线程同时访问同一资源,互斥量是信号量的一种特殊情况。
具体的用法可以参考MSDN的帮助。
写个简单使用重要区段的一个例子:// 初始化InitializeCriticalSection(FLock); // 初始化重要区段// 使用方法EnterCriticalSection(FLock); // 进入保护区//.. 需要保护的数据LeaveCriticalSection(FLock); // 释放// 释放资源DeleteCriticalSection(FLock); // 删除重要区段另外,消息也可以作为同步的一种手段。
也许你会说,消息必须要有UI,也就是说必须要有窗体才可以,其实不然,使用PostThreadMessage,然后利用SetWindowsHookEx来Hook线程的消息,处理我们发送的消息(这种方式是我在做注入后对注入进行控制时想到的方法),如下:发送方:::PostThreadMessage(hThread, WM_XXX, wPar, lPar);接收方:::SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, hInstance, dwThreadID);GetMsgProc(int code, WPARAM wParam, LPARAM lParam);{if (PMSG (lParam)^.message == WM_XXX){// Process}return ::CallNextHookEx(…);}这种方法的好处就是我们可以发送两个参数给目标。
3. 线程中常见的问题。
1) 回调函数引起的死锁。
A回调线程B中的函数,而在线程B中,再去对线程A进行操作(比如删除A)。
发生的现象:程序死掉。
2) 使用同一资源未加保护引起问题。
A和B同时去对窗体上进行绘图操作,界面可能花掉,也可能黑掉。
600)this.width = 600;">出现的现象:界面不再刷新,变成黑色。
(最好不要在子线程中去更新界面UI,可以使用消息来更新)3) 线程锁使用不当造成死锁。
线程A利用线程锁锁住资源A后,再去试图访问资源B,线程B利用线程锁锁住资源B 后试图去访问资源A。
这样就发生了线程互锁。
600) this.width = 600;">程序结果:线程死掉。
4) 未加线程保护产生异常。
线程A获取到了对象X(步骤1)的引用后被挂起(步骤2),而接下来线程B却删除了X(步骤3),线程A再次唤醒后访问对象X出错(步骤4)。
这个问题是多线程中最容易被忽略的地方,也是异常最可能发生的情况。
600)this.width = 600;">程序结果:线程异常。
5) 消息在线程同步中的问题。
先说说消息的一些基本问题(有关消息的处理部分在Windows 2000源码private\ntos\w32\ntuser\kernel\input.c文件中):消息队列的建立:线程在刚建立的时候,是没有消息队列的。
当有界面UI操作函数被调用的时候(比如CreateWindow),Windows就会为该程序建立一个消息队列,同样,通过调用PeekMessage/GetMessage可以强制操作系统为线程建立一个消息队列(参看MSDN关于PostThreadMessage的说明)。
消息的正常处理流程:线程通过GetMessage/PostMessage获取消息,然后通过TranslateMessage进行字符转换,接下来,通过User32.dll模块的帮助最后调用到相应窗口的窗口过程(RegisterClass时传入的窗口过程)。