WIN32下DELPHI中的多线程【深入VCL源码】(一)

本文详细介绍了在Win32环境下Delphi中的多线程技术,包括线程的组成部分、创建线程、线程的执行路径和线程的终止。通过源码分析,展示了如何使用TThread类来创建和管理线程,并强调了VCL线程安全的注意事项,如使用OnTerminate属性和Synchronize方法在主线程中执行UI更新。
摘要由CSDN通过智能技术生成

线程的基础知识
      线程的组成线程有两部分组成。
     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;
var
  i : integer;
begin
  
for  i : =   0  to Pinfo(info) ^ .count - 1   do
    Form1.Canvas.TextOut(Pinfo(info)
^ .x,Pinfo(info) ^ .y,inttostr(i));
  Result :
=   0 ;
end;


      上面的的代码功能很简单,你可以在程序中直接调用,例如这样:

type
  Tinfo 
=  record
    count : integer;
    x : integer;
    y : integer;
  end;
  Pinfo
=   ^ Tinfo;
...
procedure TForm1.Button4Click(Sender: TObject);
var
  ppi : Pinfo;
begin
  ppi :
= AllocMem( sizeof (tinfo));
  ppi
^ .count : =   1000000 ;
  ppi
^ .x : =   100 ;
  ppi
^ .y : =   400 ;
  MyThread(ppi);
end;


         当你在一个窗口中用这样的方式调用时,你会发现在执行的过程中,你将无法在窗口上进行其他操作,因为它工作于你程序的主线程之中。如果此时,你还希望窗口可以进行其他操作。怎么办?让它在后台工作,让它成为另一个线程,使得不同的代码可以同时运行。
    做法很简单,如果想要创建一个或多个辅助线程,只需要让一个已经在运行的线程来调用CreateThread,原型如下:

HANDLE CreateThread(
    LPSECURITY_ATTRIBUTES lpThreadAttributes, 
//  pointer to thread security attributes  
    DWORD dwStackSize,  //  initial thread stack size, in bytes 
    LPTHREAD_START_ROUTINE lpStartAddress,  //  pointer to thread function 
    LPVOID lpParameter,  //  argument for new thread 
    DWORD dwCreationFlags,  //  creation flags 
    LPDWORD lpThreadId   //  pointer to returned thread identifier 
   );


         当CreateThread,被调用时,系统创建一个线程内核对象。该线程内核对象不是线程本身,而是操作系统用来管理线程的较小的数据结构。可以将线程内核对象视为由关于线程的统计信息组成的一个小型数据结构。系统从进程的地址空间中分配内存,供线程的堆栈使用。新线程运行的进程环境与创建线程的环境相同。因此,新线程可以访问进程的内核对象的所有句柄、进程中的所有内存和在这个相同的进程中的所有其他线程的堆栈。这使得单个进程中的多个线程确实能够非常容易地互相通信。
    下面来说这个函数的几个参数:
    1、psa  此参数是指向SECURITY_ATTRIBUTES结构的指针。如果想要该线程内核对象的默认安全属性,可以(并且通常能够)传递NULL。如果希望所有的子进程能够继承该线程对象的句柄,必须设定一个SECURITY_ATTRIBUTES结构,它的bInheritHandle(是否可继承)成员被初始化为True,关于SECURITY_ATTRIBUTES,因为此文的目的不是介绍它,所以这里不做详细介绍,具体可以参考MSDN。通常使用,我们传递null就够了。
    2、cbStack 用于设定线程可以将多少地址空间用于它自己的堆栈。当调用CrateThread时,如果传递的值不是0,就能使该函数将所有的存储器保留并分配给线程的堆栈。由于所有的存储器预先作了分配,因此可以确保线程拥有指定容量的可用堆栈存储器。通常状况下,我们会设置为0。
    3、pfnStartAddr and pvParam,pfnStartAddr 参数用于指明想要新线程执行的线程函数的地址。线程函数的pvParam参数与原先传递给CreateThread的pvParam参数是相同的。CreateThread使用该参数不做别的事情,只是在线程启动执行时将该参数传递给线程函数。该参数提供了一个将初始化值传递给线程函数的手段。该初始化数据既可以是数字值,也可以是指向包含其他信息的一个数据结构的指针。此时回头再去看我上面例子上的MyThread,你会发现它由一个无类型的指针参数(用C来描述,应该是PVOID),在创建线程时,这个参数就通过pvParam来赋值。
    4、fdwcreate 此参数可以设定用于控制创建线程的其他标志。它可以是两个值中的一个。如果该值是0,那么线程创建后可以立即进行调度。如果该值是CREATE_ SUSPENDED,系统可以完整地创建线程并对它进行初始化,但是要暂停该线程的运行,这样它就无法进行调度。在DELPHI的WINDOWS.PAS单元,你可以发现它的定义
CREATE_SUSPENDED= $00000004;
    5、pdwThreadId 最后一个参数必须是Dword的一个有效地址,CreateThread
使用这个地址来存放系统分配给新线程的ID.

       有了上面这些基础,下面我们就使用createThread来创建刚才那个MyThread线程(DELPHI7);

...
// 一个自定义类型
type
  Tinfo 
=
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值