Delphi 线程

news/2025/3/17 2:47:39/文章来源:https://www.cnblogs.com/ynmsnc/p/18342650

不是原创,只是看到好的内容复制了保存下来,留着学习。

 

CreadteThred参考,同步参考,WaitForSingleObject参考,互斥参考,

 

一、在 Delphi 中使用多线程有两种方法: 调用 API、使用 TThread 类; 使用 API 的代码更简单.

1、调用 API:CreateThread()

复制代码
function CreateThread(
  lpThreadAttributes: Pointer;     {安全设置}
  dwStackSize: DWORD;          {堆栈大小}
  lpStartAddress: TFNThreadStartRoutine; {入口函数}
  lpParameter: Pointer;         {函数参数}
  dwCreationFlags: DWORD;        {启动选项}
  var lpThreadId: DWORD         {输出线程 ID }
): THandle; stdcall;          {返回线程句柄}
复制代码
 CreateThread 要使用的函数是系统级别的, 不能是某个类(譬如: TForm1)的方法, 并且有严格的格式(参数、返回值)要求, 不管你暂时是不是需要都必须按格式来;
{函数参数} 因为是系统级调用, 函数参数还要缀上 stdcall;还需要一个 var 参数来接受新建线程的 ID。
{安全设置} :
CreateThread 的第一个参数  是指向 TSecurityAttributes 结构的指针, 一般都是置为 nil, 这表示没有访问限制;
但我们在多线程编程时不需要去设置它们, 大都是使用默认设置(也就是赋值为 nil). 
{堆栈大小} :
CreateThread 的第二个参数是分配给线程的堆栈大小.
这首先这可以让我们知道: 每个线程都有自己独立的堆栈(也拥有自己的消息队列)
 
这个值都是 0, 这表示使用系统默认的大小, 默认和主线程栈的大小一样, 如果不够用会自动增长;
那主线程的栈有多大? 这个值是可以设定的: Project -> Options -> linker -> memory size
 
Delphi 为我们提供了一个类似 var 的 ThreadVar 关键字, 线程在使用 ThreadVar 声明的全局变量时会在各自的栈中留一个副本, 这样就解决了线程冲突. 不过还是尽量使用局部变量, 或者在继承 TThread 时使用类的成员变量, 因为 ThreadVar 的效率不好, 据说比局部变量能慢 10 倍.
 
{入口函数} :
线程执行的函数
该函数返回的值可以判断线程是否退出,用GetExitCodeThread 函数获取的退出码就是这个返回值!
如果线程没有退出, GetExitCodeThread 获取的退出码将是一个常量 STILL_ACTIVE (259); 这样我们就可以通过退出码来判断线程是否已退出
 
{函数参数} :线程入口函数的参数是个无类型指针(Pointer), 用它可以指定任何数据;
{启动选项} :有两个可选值:
0: 线程建立后立即执行入口函数;
CREATE_SUSPENDED: 线程建立后会挂起等待.

可用 ResumeThread 函数是恢复线程的运行; 可用 SuspendThread 再次挂起线程.
这两个函数的参数都是线程句柄, 返回值是执行前的挂起计数.

什么是挂起计数?
SuspendThread 会给这个数 +1; ResumeThread 会给这个数 -1; 但这个数最小是 0.
当这个数 = 0 时, 线程会运行; > 0 时会挂起.
如果被 SuspendThread 多次, 同样需要 ResumeThread 多次才能恢复线程的运行.
{输出线程ID} :
 1、线程的 ID 是唯一的; 而句柄可能不只一个, 譬如可以用 GetCurrentThread 获取一个伪句柄、可以用 DuplicateHandle 复制一个句柄等等.
 2、ID 比句柄更轻便.

 

复制代码
unit Unit1;interfaceusesWindows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,Dialogs;typeTForm1 = class(TForm)procedure FormMouseUp(Sender: TObject; Button: TMouseButton;Shift: TShiftState; X, Y: Integer);procedure Button2Click(Sender: TObject); end;varForm1: TForm1;implementation{$R *.dfm}varpt: TPoint; {这个坐标点将会已指针的方式传递给线程, 它应该是全局的}hThread : THandlde;  {生成的线程}function MyThreadFun(p: Pointer): Integer; stdcall;
vari: Integer;pt2: TPoint;       {因为指针参数给的点随时都在变, 需用线程的局部变量存起来}
beginpt2 := PPoint(p)^; {转换}for i := 0 to 1000000 dobeginwith Form1.Canvas do beginLock;TextOut(pt2.X, pt2.Y, IntToStr(i));Unlock;end;end;Result := 0;
end;procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;Shift: TShiftState; X, Y: Integer);
varID: DWORD;beginpt := Point(X, Y);hThread := CreateThread(nil, 0, @MyThreadFun, @pt, 0, ID);{下面这种写法更好理解, 其实不必, 因为 PPoint 会自动转换为 Pointer 的}//CreateThread(nil, 0, @MyThreadFun, Pointer(@pt), 0, ID);
end;{获取线程的退出代码, 并判断线程是否退出} 
procedure TForm1.Button2Click(Sender: TObject); 
var ExitCode: DWORD; 
begin GetExitCodeThread(hThread, ExitCode); if hThread = 0 then begin Text := '线程还未启动'; Exit; end; if ExitCode = STILL_ACTIVE then Text := Format('线程退出代码是: %d, 表示线程还未退出', [ExitCode]) else Text := Format('线程已退出, 退出代码是: %d', [ExitCode]); 
end;end.
复制代码

 

2、使用TTHread类
如果Create里面的参数是True,这样线程建立后就不会立即调用 Execute, 可以在需要的时候再用 Resume 方法执行线程。
复制代码
    procedure TForm1.Button1Click(Sender: TObject); var MyThread: TMyThread; begin MyThread := TMyThread.Create(False); end;
复制代码

OnTerminate属性:表示在线程执行完Execute之后,还没有被释放之前,要紧接着执行的方法。

复制代码
procedure TTestThread.Execute;
vari: Integer;
beginOnTerminate:= Form1.ThreadDone;    //在这里设置OnTerminate属性的值为Form1的ThreadDone方法,//表示在线程执行完Execute之后,还没有被释放之前,要紧接着执行Form1的ThreadDone方法。EnterCriticalSection(CS);for i:= 1 to MaxSize dobeginGlobalArray[i]:= GetNextNumber;Sleep(5);end;LeaveCriticalSection(CS);
end;
复制代码

 

 

二、同步

1、临界区

"临界区"(CriticalSection): 当把一段代码放入一个临界区, 线程执行到临界区时就独占了, 让其他也要执行此代码的线程先等等;

复制代码
var CS: TRTLCriticalSection;   {声明一个 TRTLCriticalSection 结构类型变量; 它应该是全局的} 
InitializeCriticalSection(CS); {初始化}
EnterCriticalSection(CS);      {开始: 轮到我了其他线程走开}
LeaveCriticalSection(CS);      {结束: 其他线程可以来了}
DeleteCriticalSection(CS);     {删除: 注意不能过早删除}var CS: TRTLCriticalSection; function MyThreadFun(p: Pointer): DWORD; stdcall; 
var i: Integer; 
beginEnterCriticalSection(CS);for i := 0 to 99 do Form1.ListBox1.Items.Add(IntToStr(i));LeaveCriticalSection(CS);Result := 0;
end; procedure TForm1.Button1Click(Sender: TObject); 
varID: DWORD;
begin CreateThread(nil, 0, @MyThreadFun, nil, 0, ID);CreateThread(nil, 0, @MyThreadFun, nil, 0, ID); CreateThread(nil, 0, @MyThreadFun, nil, 0, ID);
end;procedure TForm1.FormCreate(Sender: TObject);
begin ListBox1.Align := alLeft;InitializeCriticalSection(CS);
end;procedure TForm1.FormDestroy(Sender: TObject);
beginDeleteCriticalSection(CS);
end;
复制代码

 

Delphi 在 SyncObjs 单元给封装了一个 TCriticalSection 类, 用法差不多, 代码如下:

复制代码
uses SyncObjs; var CS: TCriticalSection; function MyThreadFun(p: Pointer): DWORD; stdcall; 
var i: Integer; 
begin CS.Enter; for i := 0 to 99 do Form1.ListBox1.Items.Add(IntToStr(i)); CS.Leave; Result := 0; 
end; procedure TForm1.Button1Click(Sender: TObject); 
var ID: DWORD; 
begin CreateThread(nil, 0, @MyThreadFun, nil, 0, ID); CreateThread(nil, 0, @MyThreadFun, nil, 0, ID); CreateThread(nil, 0, @MyThreadFun, nil, 0, ID); 
end; procedure TForm1.FormCreate(Sender: TObject); 
begin ListBox1.Align := alLeft; CS := TCriticalSection.Create; 
end; procedure TForm1.FormDestroy(Sender: TObject); 
begin CS.Free; 
end;
复制代码

2、互斥

 

互斥量(原文链接)是系统内核对象,谁拥有就谁执行。它与临界区工作很类似。不同处在于: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。)

hMutex:= CreateMutex(nil, False, nil);

 

3、用等待函数控制线程进入同步代码块:

if WaitForSingleObject(hMutex, INFINITE) = WAIT_OBJECT_0 then
begin//执行语句
end;

 

4、执行线程运行代码。

 

5、线程运行完后释放互斥量的拥有权:ReleaseMutex(hMutex: THandle);

 

6、最后关闭互斥量:CloseHandle(hMutex: THandle);

3、信号量

信号量(原文链接)是建立在互斥量的基础之上,同时加入重要特性:提供了资源计数功能,因此预定义数量的线程同时可以进入同步的代码块中。

      信号量是维护0到指定最大值之间的计数器的同步对象,当线程完成一次信号量的等待时,计数器自减1,当线程释放信号量对象时,计数器自增1。

      借用上面的图书馆例子,信号量好像是多设几把管理钥匙。每次可以设定N把钥匙同时工作,那就有N个人员可以同时办理业务。

     信号量使用的一般步骤:

1、声明一个全局的信号量名柄,如:hSem:THandle;

2、创建信号量:CreateSemphore(

                              lpSemaphoreAttributes:PSecurityAttributes;

                              lInitialCount,lMaximumCount:LongInt;

                              lpName:PChar):THandle;stdcall;

  (lpSemaphoreAttributes参数,指向TSecurityAttributes记录的指针,一般可以缺省填入nil值;

    lInitialCount参数,是信号量对象的初始计数,是0~lMaximumCount之间的数。当它大于0时,信号量就进入了信号状态,当WaiForSingleObject函数释放了一个线程,信号量计数就减1。使用ReleaseSemphore函数可以增加信号量计数;

   lMaximumCount参数,是信号量对象计数的最大值;

   lpName参数,指定信号量的名字。)

hSem:=CreateSemaphore(nil,2,3,nil);

3、用等待函数WaiForSingleObject协调线程。

4、当一个线程用完一个信号,释放。使用ReleaseSemphore(

                                                          hSemaphore:THandle;

                                                          lReleaseCount:LongInt;

                                                          lpPreviousCount:Pointer):BOOL;StdCall;

(hSemphore参数,是信号量对象句柄;

   lReleaseCount参数,要增加的信号量计数的数量;

  lpPreviousCount参数,当前资源数量的原始值,一般为nil。)

ReleaseSemaphore(hSem,1,nil); 

5、最后关闭信号量句柄,CloseHandle(hSem)。

CloseHandle(hSem);  

如果最大信号量计数为1,那么就相当于Mutex。

 三、WaitForSingleObject


DWORD WaitForSingleObject( HANDLE hHandle, DWORDdwMilliseconds);

有两个参数,分别是THandle和Timeout(毫秒单位)。

如果想要等待一条线程,那么你需要指定线程的Handle,以及相应的Timeout时间。当然,如果你想无限等待下去,Timeout参数可以指定系统常量INFINITE。

2. 使用对象

它可以等待如下几种类型的对象:

Event,Mutex,Semaphore,Process,Thread 

3. 返回类型

有三种返回类型:

WAIT_OBJECT_0, 表示等待的对象有信号。(对线程来说,表示执行结束;对互斥量对象来说,指定的对象进入信号状态,可以执行)

 WAIT_TIMEOUT, 表示等待指定时间内,对象一直没有信号。(线程没执行完;对互斥量来说,等到时间已过,对象依然是无信号状态);

WAIT_ABANDONED 表示对象有信号,但还是不能执行  一般是因为未获取到锁或其他原因(对于互斥量对象,拥有这个互斥量对象的线程在没有释放互斥量之前就已经终止,称作废弃互斥量,此时该互斥量归调用线程所拥有,并把这个互斥量设为非信号状态)

 
复制代码
function WaitForSingleObject( hHandle: THandle;      {要等待的对象句柄} dwMilliseconds: DWORD  {等待的时间, 单位是毫秒} 
): DWORD; stdcall;       {返回值如下:} WAIT_OBJECT_0  {等着了, 本例中是: 等的那个进程终于结束了} 
WAIT_TIMEOUT   {等过了点(你指定的时间), 也没等着} 
WAIT_ABANDONED {好不容易等着了, 但人家还是不让咱执行; 这一般是互斥对象} //WaitForSingleObject 的第二个参数一般给常数值 INFINITE, 表示一直等下去, 死等
复制代码
复制代码
//WaitForSingleObject的示例代码文件: unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); end; var Form1: TForm1; implementation {$R *.dfm} var hProcess: THandle; {进程句柄} {等待一个指定句柄的进程什么时候结束} 
function MyThreadFun(p: Pointer): DWORD; stdcall; 
begin if WaitForSingleObject(hProcess, INFINITE) = WAIT_OBJECT_0 then Form1.Text := Format('进程 %d 已关闭', [hProcess]); Result := 0; 
end; {启动一个进程, 并建立新线程等待它的结束} 
procedure TForm1.Button1Click(Sender: TObject); 
var pInfo: TProcessInformation; sInfo: TStartupInfo; Path: array[0..MAX_PATH-1] of Char; ThreadID: DWORD; 
begin {先获取记事本的路径} GetSystemDirectory(Path, MAX_PATH); StrCat(Path, '\notepad.exe'); {用 CreateProcess 打开记事本并获取其进程句柄, 然后建立线程监视} FillChar(sInfo, SizeOf(sInfo), 0); if CreateProcess(Path, nil, nil, nil, False, 0, nil, nil, sInfo, pInfo) then begin hProcess := pInfo.hProcess;                           {获取进程句柄} Text := Format('进程 %d 已启动', [hProcess]);  CreateThread(nil, 0, @MyThreadFun, nil, 0, ThreadID); {建立线程监视} end; 
end; 
复制代码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/778028.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

练习01

⽤普通⽤户身份登陆虚拟机上完成练习 在⽤户家⽬录下创建6个⽂件 song1.mp3 ~ song6.mp3把上出创建的songX.mp3⽂件移动到Music⽬录下(不存在则创建)在⽤户家⽬录下创建三个⽬录,分别为 friends,family,work换到friends⽬录下,把Music⽬录下的song1.mp3 ~ song3.mp3 拷⻉到当…

一个基于Java的接口快速开发框架!无需编写代码,通过UI界面编写接口!

magic-api —— 一个基于 Java 的接口快速开发框架,提供了一个类似 IDEA IDE 的 Web 界面来编写接口。大家好,我是 Java陈序员。 随着技术框架的不断发展更新,在日常开发中,我们可以借助一些第三方框架来快速实现接口开发,如 MyBatis-Plus.但是这些框架工具,往往需要定义…

文本相似度 HanPL汉语言处理

@目录前言需求简介实操开始1. 添加pom.xml依赖2. 文本相似度工具类3. 案例验证4. 验证结果总结前言 请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i、提示:以下是本篇文章正文内容,下面案例可供参考 需求 当我们需要求两个或两个以上的字符串相似度百分比时,可以…

GreatSQL 8.0.32-26 今日发布

GreatSQL 8.0.32-26 今日发布 版本信息发布时间:2024年08月05日版本号:8.0.32-26, Revision a68b3034c3d下载链接:https://gitee.com/GreatSQL/GreatSQL/releases/tag/GreatSQL-8.0.32-26用户手册:https://greatsql.cn/docs/8.0.32-26/特性增强 GreatSQL 8.0.32-26 版本在 …

「代码随想录算法训练营」第二十九天 | 动态规划 part2

62. 不同路径题目链接:https://leetcode.cn/problems/unique-paths/ 题目难度:中等 文章讲解:https://programmercarl.com/0062.不同路径.html 视频讲解:https://www.bilibili.com/video/BV1ve4y1x7Eu/ 题目状态:还是想不出 dp 数组,看题解了思路: 首先构建一个 dp 数组…

一直让 PHP 程序员懵逼的同步阻塞异步非阻塞,终于搞明白了

经常听到身边写 Java、Go 的朋友提到程序异步、非阻塞、线程、协程,让系统性能提高到百万、千万并发,使我甚是惊讶属实羡慕。大家好,我是码农先森。 经常听到身边写 Java、Go 的朋友提到程序异步、非阻塞、线程、协程,让系统性能提高到百万、千万并发,使我甚是惊讶属实羡慕…

感谢「河南图奕网络」赞助园子,成为第一家创始赞助商

在8月1日发布救援行动-赞助商计划后,我们并没有抱什么奢望,更没有妄想很快能找到赞助商,只是为救园多一点可能的希望,万一找到一家赞助商,就会多一份救园力量。没想到第2天就有幸遇到一家有意向的企业在8月1日发布救援行动-赞助商计划后,我们并没有抱什么奢望,更没有妄想…

数据跨境流动需要注意什么?怎么实现安全合规的跨境传输?

2024年3月22日,《促进和规范数据跨境流动规定》(以下简称《数据跨境规定》)正式公布并施行。数据跨境流通涉及到隐私保护、安全性和法律合规等多个方面的重要考虑因素。 具体来说,需要注意以下几点: 1、隐私保护:跨境数据流通涉及个人数据,需要确保数据在传输和存储过程…

什么样的跨网文件安全交换系统是IC设计企业需要的?

对于IC设计企业来说,设计数据和文件是企业最根本核心的数据资产,在半导体芯片的设计、仿真验证的过程中,会产生多种不同的文件类型: 需求调研:对于芯片具体需求的调研总结,如调研报告、图表、导图等; 功能与架构方案制定:作为IC设计最重要的部分,对于芯片的实现功能、…

RAG知识库之针对长文档的Raptor索引

在现有的朴素RAG应用中其只是简单的对文档进行分块后存储的向量库中,然后在使用是根据 提问问题 从查询向量库中查询相识度较高的文档快作为问题上下文提交到LLM让其根据上下文去回答用户所提问的问题。对于小文本可以直接将整个文档作为上下文或使用上篇文章所提到的多表示索…

《花100块做个摸鱼小网站! 》第一篇—买云服务器和初始化环境

一、前言 大家好呀,我是summo,前面我已经写了我为啥要做这个摸鱼小网站的原因,从这篇文章开始我会一步步跟大家聊聊我是怎么搭起这个网站的。我知道对很多新手来说,建网站可能挺头大的,不知道从哪里开始,所以我会尽量写得简单明了,让大家一看就懂,少走弯路。 咱们先从买…

Outlook如果有多个邮箱,个别邮箱设定不接收邮件的设定方法

背景:大家是不是经常用到outlook的时候,因为种种原因,某个邮箱暂时不用,或者之前的工作邮箱因为离职而导致无法正常登录,但是很多有用的邮件或者回忆都在这个邮箱里,因此不忍心删除掉,并且又不想总是被提示“邮箱登录异常……”等弹出框,现在告诉大家一个解决办法。 1、…