首页 > 其他分享 >Delphi 线程

Delphi 线程

时间:2024-08-05 09:50:57浏览次数:6  
标签:begin end nil Delphi 互斥 线程 var

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

 

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;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs;

type
  TForm1 = class(TForm)
    procedure FormMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure Button2Click(Sender: TObject); 



  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

var
  pt: TPoint; {这个坐标点将会已指针的方式传递给线程, 它应该是全局的}
  hThread : THandlde;  {生成的线程}

function MyThreadFun(p: Pointer): Integer; stdcall;
var
  i: Integer;
  pt2: TPoint;       {因为指针参数给的点随时都在变, 需用线程的局部变量存起来}
begin
  pt2 := PPoint(p)^; {转换}
  for i := 0 to 1000000 do
  begin
    with Form1.Canvas do begin
      Lock;
      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);
var
  ID: DWORD;

begin
  pt := 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;
var
  i: Integer;
begin
  OnTerminate:= Form1.ThreadDone;    //在这里设置OnTerminate属性的值为Form1的ThreadDone方法,
                                    //表示在线程执行完Execute之后,还没有被释放之前,要紧接着执行Form1的ThreadDone方法。
  EnterCriticalSection(CS);
  for i:= 1 to MaxSize do
  begin
    GlobalArray[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; 
begin
  EnterCriticalSection(CS);
  for i := 0 to 99 do Form1.ListBox1.Items.Add(IntToStr(i));
  LeaveCriticalSection(CS);
  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;
  InitializeCriticalSection(CS);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  DeleteCriticalSection(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; 
 
复制代码

标签:begin,end,nil,Delphi,互斥,线程,var
From: https://www.cnblogs.com/ynmsnc/p/18342650

相关文章

  • 使用TaskDecorator装饰器实现再线程隔离下的数据复制
    自定装饰器importorg.slf4j.MDC;importorg.springframework.core.task.TaskDecorator;importjava.util.Map;publicclassComTaskDecoratorimplementsTaskDecorator{@OverridepublicRunnabledecorate(Runnablerunnable){//主线程可执行的代......
  • 【Redis】全局命令/内部编码/浅谈单线程模型
    目录前言两个核心命令GET和SET全局命令KEYSEXISTS DELEXPIRETTLTYPE 数据结构的内部编码Redis的5中数据类型Redis数据结构和内部编码单线程架构前言Redis提供了5种数据结构,理解每种数据结构的特点对于Redis开发运维⾮常重要,同时掌握每种数据结构的常......
  • 多线程-进阶2
     博主主页: 码农派大星.  数据结构专栏:Java数据结构 数据库专栏:MySQL数据库JavaEE专栏:JavaEE关注博主带你了解更多数据结构知识1.CAS1.1CAS全称:Compareandswap比较内存和cpu寄存器中的内容,如果发现相同,就进行交换(交换的是内存和另一个寄存器的内容)......
  • 入门 PyQt6 看过来(基础)23~ 定时器和线程
    一般情况下,应用程序都是单线程运行的,但如果需要执行一个特别耗时的操作,GUI界面其他操作就无法进行,用户感觉程序没有了响应,或者windows系统也认为程序运行出现了问题并自动关闭了程序。有类似的情况应用就需要采用定时器和线程。1定时器(QTimer)如果要在应用程序中周期性地运......
  • JavaEE 第1节 认识多线程
    本节目标(全是重点,都必须掌握)1、了解什么是线程、多线程、进程以及他们之间的关系2、了解多线程的优势以及各种特性3、用Java掌握多种创建线程的方法一、线程、多线程、进程1、概念1.基本概念这三个名词的概念可以用一个餐馆的厨房和厨师来进行超级形象的比喻。想象一下......
  • Linux中的线程3
    死锁在Linux操作系统中,死锁(Deadlock)是指两个或多个进程(或线程)在执行过程中,因互相持有对方所需的资源而又都在等待对方释放资源,导致它们都无法继续执行下去的一种状态。这种僵局会浪费系统资源,甚至可能导致系统崩溃。案例://线程A和B,以及资源X和Y的初始状态资源X:空闲资......
  • 锁撤销阈值达到20次批量重偏向是针对类还是线程?撤销阈值达到20次触发的20是指撤销偏向
    先说答案, 锁的批量重偏向是针对类的,且只能触发一次,撤销阈值20次是指撤销19个对象偏向锁后再来一个对象需要撤销才会触发锁的批量重偏向,实际会撤销19个。测试过程如下:建立spring项目,要有依赖<dependency><groupId>org.openjdk.jol</groupId>......
  • 进程间通信与线程间通信的方法汇总
    目录一、进程间通信机制管道(pipe):命名管道(FIFO):消息队列(MQ):信号量(semaphore):共享内存(sharedmemory):信号(signal):内存映射(mappedmemory):内存映射和共享内存的区别Socket:二、线程间通信与同步机制Linux平台下:信号(Signal):锁机制:条件变量(ConditionVariable):......
  • Java通过redis实线多线程多用户操作时添加锁
    背景由于项目中多出涉及同步数据,同步过程就是从设备上查询数据,将数据库中该设备数据删除,将新数据导入到数据库;多次同步数据或多用户操作,会导致数据库出现重复数据,例如,两个线程同时删除设备数据,同时导入数据,就会出现双倍数据;还有线程1正在导入数据,中途线程2将线程1导入数据之前删......
  • Java使用多线程池给List赋值导致List存在空的处理
    错误示例:publicList<String>test()throwsNuMaxCloudCommonException{ExecutorServiceexecutorService=Executors.newFixedThreadPool(3);List<String>list=newArrayList<>();for(inti=0;i<3;i++){......