zoukankan      html  css  js  c++  java
  • Delphi 线程

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

    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; 
     
  • 相关阅读:
    MC9S12 硬件设计
    ESD
    选用与使用稳压二极管的介绍
    MOSFET 栅极电阻作用及其选型
    orcad常用库文件介绍
    开关电源和LDO的区别
    续流二极管的作用及选型
    为什么大电容滤低频小电容滤高频的问题
    Java常用API——时间类
    Idea问题:“marketplace plugins are not loaded”解决方案
  • 原文地址:https://www.cnblogs.com/hjdgz/p/11799175.html
Copyright © 2011-2022 走看看