2010年10月21日星期四

delphi之多线程编程(一)(转)

本文的内容取自网络,并重新加以整理,在此留存仅仅是方便自己学习和查阅。所有代码均亲自测试 delphi7下测试有效。图片均为自己制作。

多线程应该是编程工作者的基础技能, 但这个基础我从来没学过,所以仅仅是看上去会一些,明白了2+2的时候,其实我还不知道1+1。

开始本应该是一篇洋洋洒洒的文字, 不过我还是提倡先做起来, 在尝试中去理解.
先试试这个:

procedure TForm1.Button1Click(Sender: TObject); 
var 
  i: Integer; 
begin 
  for i := 0 to 500000 do 
  begin 
    Canvas.TextOut(1010, IntToStr(i)); 
  end
end

上面程序运行时, 我们的窗体基本是 "死" 的, 可以在你在程序运行期间拖动窗体试试...

Delphi 为我们提供了一个简单的办法(Application.ProcessMessages)来解决这个问题:
procedure TForm1.Button1Click(Sender: TObject); 
var 
  i: Integer; 
begin 
  for i := 0 to 500000 do 
  begin 
    Canvas.TextOut(1010, IntToStr(i)); 
    Application.ProcessMessages; 
  end
end

这个 Application.ProcessMessages; 一般用在比较费时的循环中, 它会检查并先处理消息队列中的其他消息.

但这算不上多线程, 譬如: 运行中你拖动窗体, 循环会暂停下来...

在使用多线程以前, 让我们先简单修改一下程序:
function MyFun: Integer; 
var 
  i: Integer; 
begin 
  for i := 0 to 500000 do 
  begin 
    Form1.Canvas.Lock; 
    Form1.Canvas.TextOut(1010, IntToStr(i)); 
    Form1.Canvas.Unlock; 
  end
  Result := 0
end
 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  MyFun; 
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; 
var 
  i: Integer; 
begin 
  for i := 0 to 500000 do 
  begin 
    Form1.Canvas.Lock; 
    Form1.Canvas.TextOut(1010, IntToStr(i)); 
    Form1.Canvas.Unlock; 
  end
  Result := 0
end
 
procedure TForm1.Button1Click(Sender: TObject); 
var 
  ID: THandle; 
begin 
  CreateThread(nil0, @MyFun, nil0, ID); 
end

代码分析:
CreateThread 一个线程后, 算上原来的主线程, 这样程序就有两个线程、是标准的多线程了;
CreateThread 第三个参数是函数指针, 新线程建立后将立即执行该函数, 函数执行完毕, 系统将销毁此线程从而结束多线程的故事.

CreateThread 要使用的函数是系统级别的, 不能是某个类(譬如: TForm1)的方法, 并且有严格的格式(参数、返回值)要求, 不管你暂时是不是需要都必须按格式来;
因为是系统级调用, 还要缀上 stdcall, stdcall 是协调参数顺序的, 虽然这里只有一个参数没有顺序可言, 但这是使用系统函数的惯例.

CreateThread 还需要一个 var 参数来接受新建线程的 ID, 尽管暂时没用, 但这也是格式; 其他参数以后再说吧.

这样一个最简单的多线程程序就出来了, 咱们再用 TThread 类实现一次
type 
  TMyThread = class(TThread) 
  protected 
    procedure Execute; override; 
  end
 
procedure TMyThread.Execute; 
var 
  i: Integer; 
begin 
  FreeOnTerminate := True; {这可以让线程执行完毕后随即释放} 
  for i := 0 to 500000 do 
  begin 
    Form1.Canvas.Lock; 
    Form1.Canvas.TextOut(1010, IntToStr(i)); 
    Form1.Canvas.Unlock; 
  end
end
 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  TMyThread.Create(False); 
end
 
TThread 类有一个抽象方法(Execute), 因而是个抽象类, 抽象类只能继承使用, 上面是继承为 TMyThread.

继承 TThread 主要就是实现抽象方法 Execute(把我们的代码写在里面), 等我们的 TMyThread 实例化后, 首先就会执行 Execute 方法中的代码.

按常规我们一般这样去实例化:
procedure TForm1.Button1Click(Sender: TObject); 
var 
  MyThread: TMyThread; 
begin 
  MyThread := TMyThread.Create(False); 
end
因为 MyThread 变量在这里毫无用处(并且编译器还有提示), 所以不如直接写做 TMyThread.Create(False);

我们还可以轻松解决一个问题, 如果: TMyThread.Create(True) ?
这样线程建立后就不会立即调用 Execute, 可以在需要的时候再用 Resume 方法执行线程, 譬如:
procedure TForm1.Button1Click(Sender: TObject); 
var 
  MyThread: TMyThread; 
begin 
  MyThread := TMyThread.Create(True); 
  MyThread.Resume; 
end
 
//可简化为: 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  with 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); 
begin 
  ExitThread(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 = record 
  ContextFlags: 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 两个函数.

//上面图片中演示的代码。 
unit Unit1; 
 
interface 
 
uses 
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
  Dialogs, StdCtrls, ExtCtrls; 
 
type 
  TForm1 = class(TForm) 
    Button1: TButton; 
    Button2: TButton; 
    Button3: TButton; 
    Timer1: TTimer; 
    procedure Button1Click(Sender: TObject); 
    procedure Button2Click(Sender: TObject); 
    procedure Button3Click(Sender: TObject); 
    procedure FormCreate(Sender: TObject); 
    procedure Timer1Timer(Sender: TObject); 
  end
 
var 
  Form1: TForm1; 
 
implementation 
 
{$R *.dfm} 
 
var 
  hThread: THandle; {线程句柄} 
  num: Integer;     {全局变量, 用于记录随机数} 
 
{线程入口函数} 
function MyThreadFun(p: Pointer): Integer; stdcall; 
begin 
  while True do {假如线程不挂起, 这个循环将一直循环下去} 
  begin 
    num := Random(100); 
  end
  Result := 0
end
 
{建立并挂起线程} 
procedure TForm1.Button1Click(Sender: TObject); 
var 
  ID: DWORD; 
begin 
  hThread := CreateThread(nil0, @MyThreadFun, nil, CREATE_SUSPENDED, ID); 
  Button1.Enabled := False; 
end
 
{唤醒并继续线程} 
procedure TForm1.Button2Click(Sender: TObject); 
begin 
  ResumeThread(hThread); 
end
 
{挂起线程} 
procedure TForm1.Button3Click(Sender: TObject); 
begin 
  SuspendThread(hThread); 
end
 
procedure TForm1.FormCreate(Sender: TObject); 
begin 
  Timer1.Interval := 100
end
 
procedure TForm1.Timer1Timer(Sender: TObject); 
begin 
  Text := IntToStr(num); 
end
 
end.
㈢、入口函数的参数
function CreateThread( 
  lpThreadAttributes: Pointer; 
  dwStackSize: DWORD; 
  lpStartAddress: TFNThreadStartRoutine; 
  lpParameter: Pointer;  {入口函数的参数} 
  dwCreationFlags: DWORD; 
  var lpThreadId: DWORD 
): THandle; stdcall;
线程入口函数的参数是个无类型指针(Pointer), 用它可以指定任何数据; 本例是把鼠标点击窗体的坐标传递给线程的入口函数, 每次点击窗体都会创建一个线程.

运行效果图:

//上面演示的代码 
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); 
  end
 
var 
  Form1: TForm1; 
 
implementation 
 
{$R *.dfm} 
 
var 
  pt: TPoint; {这个坐标点将会已指针的方式传递给线程, 它应该是全局的} 
 
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); 
  CreateThread(nil0, @MyThreadFun, @pt, 0, ID); 
  {下面这种写法更好理解, 其实不必, 因为 PPoint 会自动转换为 Pointer 的} 
  //CreateThread(nil, 0, @MyThreadFun, Pointer(@pt), 0, ID); 
end
 
end.

这个例子还有不严谨的地方: 当一个线程 Lock 窗体的 Canvas 时, 其他线程在等待; 线程在等待时, 其中的计数也还在增加. 这也就是说: 现在并没有去处理线程的同步; 同步是多线程中最重要的课题, 快到了.

另外有个小技巧: 线程函数的参数是个 32 位(4个字节)的指针, 仅就本例来讲, 可以让它的 "高16位" 和 "低16位" 分别携带 X 和 Y; 这样就不需要哪个全局的 pt 变量了.
其实在 Windows 的消息中就是这样传递坐标的, 在 Windows 的消息中一般高字节是 Y、低字节是 X; 咱们这么来吧, 这样还可以使用给消息准备的一些方便的函数.

重写本例代码(当然运行效果和窗体文件都是一样的):
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); 
  end
 
var 
  Form1: TForm1; 
 
implementation 
 
{$R *.dfm} 
 
function MyThreadFun(p: Pointer): Integer; stdcall; 
var 
  i: Integer; 
  x,y: Word; 
begin 
  x := LoWord(Integer(p)); 
  y := HiWord(Integer(p)); 
  {如果不使用 LoWord、HiWord 函数可以像下面这样: } 
  //x := Integer(p); 
  //y := Integer(p) shr 16; 
  for i := 0 to 1000000 do 
  begin 
    with Form1.Canvas do begin 
      Lock; 
      TextOut(x, y, IntToStr(i)); 
      Unlock; 
    end
  end
  Result := 0
end
 
procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton; 
  Shift: TShiftState; X, Y: Integer); 
var 
  ID: DWORD; 
  num: Integer; 
begin 
  num := MakeLong(X, Y); 
  {如果不使用 MekeLong、MakeWParam、MakeLParam、MakeResult 等函数, 可以像下面这样: } 
  //num := Y shl 16 + X; 
  CreateThread(nil0, @MyThreadFun, Ptr(num), 0, ID); 
  {上面的 Ptr 是专门将一个数字转换为指针的函数, 当然也可以这样: } 
  //CreateThread(nil, 0, @MyThreadFun, Pointer(num), 0, ID); 
end
 
end.
㈣、入口函数的指针
function CreateThread( 
  lpThreadAttributes: Pointer; 
  dwStackSize: DWORD; 
  lpStartAddress: TFNThreadStartRoutine; {入口函数的指针} 
  lpParameter: Pointer;  
  dwCreationFlags: DWORD; 
  var lpThreadId: DWORD 
): THandle; stdcall;


到 了入口函数了, 学到这个地方, 我查了一个入口函数的标准定义, 这个函数的标准返回值应该是 DWORD, 不过这函数在 Delphi 的 System 单元定义的是: TThreadFunc = function(Parameter: Pointer): Integer; 我以后会尽量使用 DWORD 做入口函数的返回值.

这个返回值有什么用呢?
等线程退出后, 我们用 GetExitCodeThread 函数获取的退出码就是这个返回值!

如果线程没有退出, GetExitCodeThread 获取的退出码将是一个常量 STILL_ACTIVE (259); 这样我们就可以通过退出码来判断线程是否已退出.

还有一个问题: 前面也提到过, 线程函数不能是某个类的方法! 假如我们非要线程去执行类中的一个方法能否实现呢?
尽管可以用 Addr(类名.方法名) 或 MethodAddress('published 区的方法名') 获取类中方法的地址, 但都不能当做线程的入口函数, 原因可能是因为类中的方法的地址是在实例化为对象时动态分配的.
后来换了个思路, 其实很简单: 在线程函数中再调用方法不就得了, 估计 TThread 也应该是这样.

下面的例子就尝试了用线程调用 TForm1 类中的方法, 并测试了退出码的相关问题.

unit Unit1; 
 
interface 
 
uses 
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
  Dialogs, StdCtrls; 
 
type 
  TForm1 = class(TForm) 
    Button1: TButton; 
    Button2: TButton; 
    procedure Button1Click(Sender: TObject); 
    procedure Button2Click(Sender: TObject); 
    private 
      procedure FormProc; {准备给线程使用的方法} 
  end
 
var 
  Form1: TForm1; 
 
implementation 
 
{$R *.dfm} 
 
var 
  hThread: THandle; 
 
{线程入口函数} 
function MyThreadFun(p: Pointer): DWORD; stdcall; 
begin 
  Form1.FormProc; {调用 TForm1 类的方法} 
  Result := 99;   {这个返回值将成为线程的退出代码, 99 是我随意给的数字} 
end
 
{TForm1 的方法, 本例中是给线程的入口函数调用的} 
procedure TForm1.FormProc; 
var 
  i: Integer; 
begin 
  for i := 0 to 200000 do 
  begin 
    with Form1.Canvas do begin 
      Lock; 
      TextOut(1010, IntToStr(i)); 
      Unlock; 
    end
  end
end
 
{建立并执行线程} 
procedure TForm1.Button1Click(Sender: TObject); 
var 
  ID: DWORD; 
begin 
  hThread := CreateThread(nil0, @MyThreadFun, nil0, 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.
㈤、堆栈大小
function CreateThread( 
  lpThreadAttributes: Pointer; 
  dwStackSize: DWORD;  {堆栈大小} 
  lpStartAddress: TFNThreadStartRoutine;  
  lpParameter: Pointer;  
  dwCreationFlags: DWORD; 
  var lpThreadId: DWORD 
): THandle; stdcall;


CreateThread 的第二个参数是分配给线程的堆栈大小.
这首先这可以让我们知道: 每个线程都有自己独立的堆栈(也拥有自己的消息队列).

什么是堆栈? 其实堆是堆、栈是栈, 有时 "栈" 也被叫做 "堆栈".
它们都是进程中的内存区域, 主要是存取方式不同(栈:先进后出; 堆:先进先出);
"栈"(或叫堆栈)适合存取临时而轻便的变量, 主要用来储存局部变量; 譬如 for i := 0 to 99 do 中的 i 就只能存于栈中, 你把一个全局的变量用于 for 循环计数是不可以的.

现在我们知道了线程有自己的 "栈", 并且在建立线程时可以分配栈的大小.

前面所有的例子中, 这个值都是 0, 这表示使用系统默认的大小, 默认和主线程栈的大小一样, 如果不够用会自动增长;
那主线程的栈有多大? 这个值是可以设定的: Project -> Options -> linker -> memory size(如图)

栈是私有的但堆是公用的, 如果不同的线程都来使用一个全局变量有点乱套;
为 解决这个问题 Delphi 为我们提供了一个类似 var 的 ThreadVar 关键字, 线程在使用 ThreadVar 声明的全局变量时会在各自的栈中留一个副本, 这样就解决了冲突. 不过还是尽量使用局部变量, 或者在继承 TThread 时使用类的成员变量, 因为 ThreadVar 的效率不好, 据说比局部变量能慢 10 倍.

在下面的例子就测试了用 var 和 ThreadVar 定义变量的不同.
使用 var 效果图:

使用 ThreadVar 效果图:

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 num: Integer;     {全局变量} 
threadvar num: Integer; {支持多线程的全局变量} 
 
function MyThreadFun(p: Pointer): DWORD; stdcall; 
var 
  py: Integer; 
begin 
  py := Integer(p); 
  while True do 
  begin 
    Inc(num); 
    with Form1.Canvas do begin 
      Lock; 
      TextOut(20, py, IntToStr(num)); 
      Unlock; 
    end
    Sleep(1000); {然线程挂起 1 秒钟再继续} 
  end
end
 
procedure TForm1.Button1Click(Sender: TObject); 
var 
  ID: DWORD; 
begin 
  {借入口函数的参数传递了一个坐标点中的 Y 值, 以让各线程把结果输出在不同位置} 
  CreateThread(nil0, @MyThreadFun, Ptr(20), 0, ID); 
  CreateThread(nil0, @MyThreadFun, Ptr(40), 0, ID); 
  CreateThread(nil0, @MyThreadFun, Ptr(60), 0, ID); 
end
 
end.
㈥、安全设置
function CreateThread( 
  lpThreadAttributes: Pointer; {安全设置} 
  dwStackSize: DWORD; 
  lpStartAddress: TFNThreadStartRoutine;  
  lpParameter: Pointer;  
  dwCreationFlags: DWORD; 
  var lpThreadId: DWORD 
): THandle; stdcall;
CreateThread 的第一个参数 lpThreadAttributes 是指向 TSecurityAttributes 结构的指针, 一般都是置为 nil, 这表示没有访问限制; 该结构的定义是:
//TSecurityAttributes(又名: SECURITY_ATTRIBUTES、_SECURITY_ATTRIBUTES) 
_SECURITY_ATTRIBUTES = record 
  nLength: DWORD;                {结构大小} 
  lpSecurityDescriptor: Pointer; {默认 nil; 这是另一个结构 TSecurityDescriptor 的指针} 
  bInheritHandle: BOOL;          {默认 False, 表示不可继承} 
end
 
//TSecurityDescriptor(又名: SECURITY_DESCRIPTOR、_SECURITY_DESCRIPTOR) 
_SECURITY_DESCRIPTOR = record 
  Revision: Byte; 
  Sbz1: Byte; 
  Control: SECURITY_DESCRIPTOR_CONTROL; 
  Owner: PSID; 
  Group: PSID; 
  Sacl: PACL; 
  Dacl: PACL; 
end;
够复杂的, 但我们在多线程编程时不需要去设置它们, 大都是使用默认设置(也就是赋值为 nil).

我觉得有必要在此刻了解的是: 建立系统内核对象时一般都有这个属性(TSecurityAttributes);
在接下来多线程的课题中要使用一些内核对象, 不如先盘点一下, 到时碰到这个属性时给个 nil 即可, 不必再费神.
{建立事件} 
function CreateEvent( 
  lpEventAttributes: PSecurityAttributes; {!} 
  bManualReset: BOOL; 
  bInitialState: BOOL; 
  lpName: PWideChar 
): THandle; stdcall; 
 
{建立互斥} 
function CreateMutex( 
  lpMutexAttributes: PSecurityAttributes; {!} 
  bInitialOwner: BOOL; 
  lpName: PWideChar 
): THandle; stdcall; 
 
{建立信号} 
function CreateSemaphore( 
  lpSemaphoreAttributes: PSecurityAttributes; {!} 
  lInitialCount: Longint; 
  lMaximumCount: Longint; 
  lpName: PWideChar 
): THandle; stdcall; 
 
{建立等待计时器} 
function CreateWaitableTimer( 
  lpTimerAttributes: PSecurityAttributes; {!} 
  bManualReset: BOOL; 
  lpTimerName: PWideChar 
): THandle; stdcall; 
上 面的四个系统内核对象(事件、互斥、信号、计时器)都是线程同步的手段, 从这也能看出处理线程同步的复杂性; 不过这还不是全部, Windows Vista 开始又增加了 Condition variables(条件变量)、Slim Reader-Writer Locks(读写锁)等同步手段.

不过最简单、最轻便(速度最快)的同步手段还是 CriticalSection(临界区), 但它不属于系统内核对象, 当然也就没有句柄、没有 TSecurityAttributes 这个安全属性, 这也导致它不能跨进程使用; 不过写多线程时一般不用跨进程, 所以 CriticalSection 应该是最常用的同步手段.

二、临界区。
先看一段程序, 代码文件:
unit Unit1; 
 
interface 
 
uses 
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
  Dialogs, StdCtrls; 
 
type 
  TForm1 = class(TForm) 
    ListBox1: TListBox; 
    Button1: TButton; 
    procedure FormCreate(Sender: TObject); 
    procedure Button1Click(Sender: TObject); 
  end
 
var 
  Form1: TForm1; 
 
implementation 
 
{$R *.dfm} 
 
function MyThreadFun(p: Pointer): DWORD; stdcall; 
var 
  i: Integer; 
begin 
  for i := 0 to 99 do Form1.ListBox1.Items.Add(IntToStr(i)); 
  Result := 0
end
 
procedure TForm1.Button1Click(Sender: TObject); 
var 
  ID: DWORD; 
begin 
  CreateThread(nil0, @MyThreadFun, nil0, ID); 
  CreateThread(nil0, @MyThreadFun, nil0, ID); 
  CreateThread(nil0, @MyThreadFun, nil0, ID); 
end
 
procedure TForm1.FormCreate(Sender: TObject); 
begin 
  ListBox1.Align := alLeft; 
end
 
end.
在这段程序中, 有三个线程几乎是同时建立, 向窗体中的 ListBox1 中写数据, 最后写出的结果是这样的:

能不能让它们别打架, 一个完了另一个再来? 这就要用到多线程的同步技术.
前面说过, 最简单的同步手段就是 "临界区".

先说这个 "同步"(Synchronize), 首先这个名字起的不好, 我们好像需要的是 "异步"; 其实异步也不准确...
管它叫什么名字呢, 它的目的就是保证不冲突、有次序、都发生.

"临界区"(CriticalSection): 当把一段代码放入一个临界区, 线程执行到临界区时就独占了, 让其他也要执行此代码的线程先等等; 这和前面用的 Lock 和 UnLock 差不多; 使用格式如下:
var CS: TRTLCriticalSection;   {声明一个 TRTLCriticalSection 结构类型变量; 它应该是全局的} 
InitializeCriticalSection(CS); {初始化} 
EnterCriticalSection(CS);      {开始: 轮到我了其他线程走开} 
LeaveCriticalSection(CS);      {结束: 其他线程可以来了} 
DeleteCriticalSection(CS);     {删除: 注意不能过早删除} 
 
//也可用 TryEnterCriticalSection 替代 EnterCriticalSection.
用上临界区, 重写上面的代码, 运行效果图:

//用临界区重写后的代码文件: 
unit Unit1; 
 
interface 
 
uses 
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
  Dialogs, StdCtrls; 
 
type 
  TForm1 = class(TForm) 
    ListBox1: TListBox; 
    Button1: TButton; 
    procedure FormCreate(Sender: TObject); 
    procedure FormDestroy(Sender: TObject); 
    procedure Button1Click(Sender: TObject); 
  end
 
var 
  Form1: TForm1; 
 
implementation 
 
{$R *.dfm} 
 
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(nil0, @MyThreadFun, nil0, ID); 
  CreateThread(nil0, @MyThreadFun, nil0, ID); 
  CreateThread(nil0, @MyThreadFun, nil0, ID); 
end
 
procedure TForm1.FormCreate(Sender: TObject); 
begin 
  ListBox1.Align := alLeft; 
  InitializeCriticalSection(CS); 
end
 
procedure TForm1.FormDestroy(Sender: TObject); 
begin 
  DeleteCriticalSection(CS); 
end
 
end.
Delphi 在 SyncObjs 单元给封装了一个 TCriticalSection 类, 用法差不多, 代码如下:
unit Unit1; 
 
interface 
 
uses 
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
  Dialogs, StdCtrls; 
 
type 
  TForm1 = class(TForm) 
    ListBox1: TListBox; 
    Button1: TButton; 
    procedure FormCreate(Sender: TObject); 
    procedure FormDestroy(Sender: TObject); 
    procedure Button1Click(Sender: TObject); 
  end
 
var 
  Form1: TForm1; 
 
implementation 
 
{$R *.dfm} 
 
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(nil0, @MyThreadFun, nil0, ID); 
  CreateThread(nil0, @MyThreadFun, nil0, ID); 
  CreateThread(nil0, @MyThreadFun, nil0, ID); 
end
 
procedure TForm1.FormCreate(Sender: TObject); 
begin 
  ListBox1.Align := alLeft; 
  CS := TCriticalSection.Create; 
end
 
procedure TForm1.FormDestroy(Sender: TObject); 
begin 
  CS.Free; 
end
 
end.
三、等待函数 WaitForSingleObject
一 下子跳到等待函数 WaitForSingleObject, 是因为下面的 Mutex、Semaphore、Event、WaitableTimer 等同步手段都要使用这个函数; 不过等待函数可不止 WaitForSingleObject 它一个, 但它最简单.
function WaitForSingleObject( 
  hHandle: THandle;      {要等待的对象句柄} 
  dwMilliseconds: DWORD  {等待的时间, 单位是毫秒} 
): DWORD; stdcall;       {返回值如下:} 
 
WAIT_OBJECT_0  {等着了, 本例中是: 等的那个进程终于结束了} 
WAIT_TIMEOUT   {等过了点(你指定的时间), 也没等着} 
WAIT_ABANDONED {好不容易等着了, 但人家还是不让咱执行; 这一般是互斥对象} 
 
//WaitForSingleObject 的第二个参数一般给常数值 INFINITE, 表示一直等下去, 死等. 

WaitForSingleObject 等待什么? 在多线程里就是等待另一个线程的结束, 快来执行自己的代码; 不过它可以等待的对象可不止线程; 这里先来一个等待另一个进程结束的例子, 运行效果图:

//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-1of Char; 
  ThreadID: DWORD; 
begin 
  {先获取记事本的路径} 
  GetSystemDirectory(Path, MAX_PATH); 
  StrCat(Path, '\notepad.exe'); 
 
  {用 CreateProcess 打开记事本并获取其进程句柄, 然后建立线程监视} 
  FillChar(sInfo, SizeOf(sInfo), 0); 
  if CreateProcess(Path, nilnilnil, False, 0nilnil, sInfo, pInfo) then 
  begin 
    hProcess := pInfo.hProcess;                           {获取进程句柄} 
    Text := Format('进程 %d 已启动', [hProcess]);  
    CreateThread(nil0, @MyThreadFun, nil0, ThreadID); {建立线程监视} 
  end
end
 
end.

JavaScript实现的3D球面标签云效果(转)

摘自:http://topic.csdn.net/u/20101021/10/33959060-2c51-4e15-aba7-fc770a39e328.html?43329

这个效果是由 妙味QQ群[116279832] 里的 "小小丫头" 提出来的。

她说一般这个效果都是由 FLASH 实现的,能不能由 JavaScript 实现呢?

我们也十分喜欢这个效果,就花了一些时间写出来了,如图所示:




效果预览点这里:http://www.miaov.com/miaov_demo/3dLable/miaov_demo.html
下载效果点这里:http://www.miaov.com/miaov_demo/3dLable/3dLable.rar


使用说明:

一般需要修改的,应该是3D球面的大小,也就是半径,修改了大小,就可以随意放进自己页面的任何地方。

半径大小修改:

下载后,打开 miaov.js 文件,第一行:var radius = 120; 这就是半径大小值了。

文字的修改在 miaov_demo.html 文件中,可以任意添加关键词的个数;

如果希望修改文字颜色、鼠标经过的样式,可以打开 miaov_style.css 文件,修改相关的样式即可。

效果原理:

制作这个效果需要重新回顾一下高中的数学知识:

球坐标系
例如:
r∈[0,+∞),
φ∈[0, 2π],
θ∈[0, π] .
r = 常数,即以原点为心的球面;
θ= 常数,即以原点为顶点、z轴为轴的圆锥面;
φ= 常数,即过z轴的半平面。
……
在这里,我们就不展开讲了,由数学老师来说明更合适些^_^
有兴趣的朋友可以看看百度百科的球坐标系原理:http://baike.baidu.com/view/1196991.htm

sql左联右联内联的通俗解释

摘自:http://xifeng91.javaeye.com/blog/380516

例表a
aid adate
1 a1
2 a2
3 a3
表b
bid bdate
1 b1
2 b2
4 b4

 

inner join

 

两个表a,b相连接,要取出id相同的字段
select * from a inner join b on a.aid = b.bid这是仅取出匹配的数据.
此时的取出的是:
1 a1 b1
2 a2 b2


那么left join 指:
select * from a left join b on a.aid = b.bid
首先取出a表中所有数据,然后再加上与a,b匹配的的数据
此时的取出的是:
1 a1 b1
2 a2 b2
3 a3 空字符


同样的也有right join
指的是首先取出b表中所有数据,然后再加上与a,b匹配的的数据
此时的取出的是:
1 a1 b1
2 a2 b2
4 空字符 b4

2010年10月19日星期二

adoquery adotable中Filter的用法(转)

摘自:http://anony3721.blog.163.com/blog/static/511974201022181232502/

当我们在操作数据集时,往往需要对数据进行筛眩例如:一个名为Customer的数据 表,它具有CustNo、CustName、Country、Address、Phone、State、TaxRate等字段,如果只想查看国别为 China或顾客号大于1000的顾客记录,就需要对数据集进行过滤。经总结,有下面这些过滤方法:
一、利用TTable和TQuery的Filter属性
1.在设计时设置Filter属性
例如,设置Filter为:Country=′China′然后改变Filtered属性为True(注意,Filtered为True时过滤才有效)。则只能看到对应的Country字段内容为‘China’的记录。

设置Filter时可以使用的操作符有:<、>、<=、>=、=、<>、AND、OR、NOT
例如,设置Filter为:CustNo>=1000andCustNo<=5000,则只能看到顾客号在1000与5000之间的顾客记录。
2.在程序运行期间进行动态过滤
要在程序运行时改变Filter属性,这包括两种情况:
(1)操作符右边为常量,例如:Table1 Filter:=′State′+′=′+′′′HI′′′;
注意:字符串常量必须用三对单引号括起来。
(2)操作符右边不为常量,可能是通过一个变量指定的值,或由一输入框给出的值。这时需要用到Format函数。其代码形式为:Table1 Filter:=Format(′State′+′=′+′′′%S′′′,[StateValue]);其中StateValue为已经赋值的一个字符串变量,也可以为其他形式,例如:Edit1 Text。

、用ApplyRange筛选数据集的记录
执行下面这段代码,将只能看到顾客号在1000至5000之间的顾客记录。组成该例程的几个过程为:
ApplyRange,SetRangeStart,SetRangeEnd。
Table1 SetRangeStart;
Table1[′CustNo′]:=1000;
Table1 SetRangeEnd;
Table1[′CustNo′]:=5000;
Table1 ApplyRange;
注意:该过程只适用于索引的字段。如果想基于非索引字段筛选,则不妨使用一点小花招:建立假索引。实现的方法为:Table1.IndexFieldNames:=字段名;Delphi还提供了简单的调用SetRangeStart、SetRangeEnd和ApplyRange的方法,例如:Table1.SetRange([Edit1.Text],[Edit2.Text]);

三、用OnFilterRecord事件筛选OnFilterRecord事件允许按非键控字段建立筛选程序,例如:
procedure TForm1.Table1FilterRecord(DataSet:TDataSet;varAccept:Boolean);
begin
Accept:=DataSet[′State′]=′CA′;
end;
四、用TQuery控件的SQL语句
1.SQL语句中不包含变量和参数
Select*fromCustomer
WhereCustNo>=1000and CustNo<=5000

2.SQL语句中包含参数
Select*fromCustomer
WhereCustNo>=:CustNo
在运行期间给参数CustNo赋值。
3.SQL语句中包含变量
这时向TQuery控件添加SQL语句的代码应当这样写:

Query1.Close;
Query1.SQL.Clear;
Query1.SQL.Add(Format(′Select*fromCustomer′+′′+′whereState=′+′′′%S′′′,[StateValue]));
Query1.Open;在上面的四种方法中,第四种功能最强大,使用最灵活


adoquery filter使用

//hanshuming   like   'g%'  

编辑器中filter属性值过滤所有G打头的

//

//代码中adot.Filter:='hanshuming'+' '+'like'+' '+'''G%''';
//adot.Filtered:=true;

代码中字符串三对单引号 属性编辑器中一对单引号

利用字符串变量要4对''''+变量+'''' 重要

2)

hanshuming='getmodulehandle'

//编辑器中FILTER属性函数名是GETMODULEHANDLE的

//代码中'hanshuming='+'''getmodulehandle''';

下面是代码中:

adot.Filter:='hanshuming='+'''getmodulehandle''';

//上面那个参数是字符串

adot.Filter:='hanshuming='+''''+n1.text+'''';

//下面这个参数是字符变量

2010年10月15日星期五

AnsiString?PChar?赋值?转换?(转)

摘自:http://www.computernews.com.cn/Article/2004-10-13/4103.html

记得原来给aiirii(ari)胡诌了一通PChar和string,一直感觉心里有愧,现在好好说一说这个知识点。

AnsiString?PChar?赋值?转换?

AnsiString,Delphi开发人员指南这么解释的,AnsiString就是指向堆中字符串结构的指针,显示了AnsiString的分配情况。
帮助上这么解释:长字符串保存有成员数量,PChar没有,长字符串(相对于ShortString类型来说,这里就是AnsiString)是Null 结尾的,并且包含了引用记数,PChar就是一个简单的Null结尾的字符串。AnsiString之间的赋值是赋值数据,而PChar赋值是改变指针的 指向。

同样存储LIKE,两种类型差异:

string简图:
--------------------------------------------------------
| 引用计数位 | 长度位 | L | I | K | E | #0 |
--------------------------------------------------------

PChar简图:
-------------------------
| L | I | K | E | #0 |
-------------------------

而string的指针通常指向数据位第一个,访问前面的引用计数和长度位的时候word ptr [eax-$04], dword ptr [eax-$08],这一类的代码。

它们之间的转换,Delphi开发人员指南里面是这么写的:不需要用StrPas和StrPCopy去来回转换 string和PChar类型。我们在前面讲过,可以把AnsiString强制转化成PChar。在想把PChar转换成AnsiString时,可以 直接赋值:StringVar := PCharVar;

StrPas的原型是什么?我悄悄地告诉你啊,不要告诉别人哦!:〉
function StrPas(const Str: PChar): string;
begin
Result := Str;
end;
哈哈,搞笑吧。Borland弄了这么一个东西…… -_-bb
其实是为了兼容而设立的。

StrPCopy相对来讲就比较复杂了,是把string强制转化成PChar之后按照string的长度进行PChar之间复制。

function StrPCopy(Dest: PChar; const Source: string): PChar;
begin
Result := StrLCopy(Dest, PChar(Source), Length(Source));
end;

而StrLCopy类似于C语言里面的strcpy这种用来复制内容的函数,所以尽量用StrLCopy来给PChar赋值,
因为会保护现场,用赋值号,就改变了指针的指向了。

有关Delphi开发人员指南里面的介绍,我写了这么一些代码来验证一下:
var
P: PChar;
S: string;
begin
S := '1234567ABCDE';
P := PChar(S);
S := P;
ShowMessage(S);
ShowMessage(P);
ShowMessage(IntToStr(SizeOf(S)));
end;

当执行到S := '1234567ABCDE';时,汇编代码如下:

第一句:S := '1234567ABCDE';
0045B130 8D45FC lea eax,[ebp-$04]
0045B133 BAB0B14500 mov edx,$0045b1b0
0045B140 E8E791FAFF call @LStrLAsg

LStrLAsg就是字符串赋值地内建函数。正验证了帮助里面的那句string之间是数据Copy。

第二句:P := PChar(S);
0045B13D 8B45FC mov eax,[ebp-$04]
0045B140 E8FF95FAFF call @LStrToPChar
0045B145 8BD8 mov ebx,eax

进入这一句了,LStrToPChar是不是很容易理解呢,string转化为PChar。然后简单的赋地址,指针改变指向。

好了该由PChar转换到string了,这个是一个重点。

第三句:S := P;
0045B147 8D45FC lea eax,[ebp-$04]
0045B14A 8BD3 mov edx,ebx
0045B14C E83393FAFF call @LStrFromPChar

LStrFromPChar是由PChar转换string的一个内建函数。

进入LStrFromPChar的代码
@LStrFromPChar
00404484 31C9 xor ecx,ecx
00404486 85D2 test edx,edx
00404488 7421 jz +$21
0040448A 52 push edx
0040448B 3A0A cmp cl,[edx]
0040448D 7417 jz +$17
0040448F 3A4A01 cmp cl,[edx+$01]
00404492 7411 jz +$11
00404494 3A4A02 cmp cl,[edx+$02]
00404497 740B jz +$0b
00404499 3A4A03 cmp cl,[edx+$03]
0040449C 7405 jz +$05
0040449E 83C204 add edx,$04
004044A1 EBE8 jmp -$18 //循环得到字符串长度。
004044A3 42 inc edx
004044A4 42 inc edx
004044A5 42 inc edx
004044A6 89D1 mov ecx,edx
004044A8 5A pop edx
004044A9 29D1 sub ecx,edx //到这里得到了PChar的长度。
004044AB E9D4FEFFFF jmp @LStrFromPCharLen
004044B0 C3 ret

然后处理成string的长度的转化:

@LStrFromPCharLen
00404384 53 push ebx
00404385 56 push esi
00404386 57 push edi
00404387 89C3 mov ebx,eax
00404389 89D6 mov esi,edx
0040438B 89CF mov edi,ecx
0040438D 89F8 mov eax,edi
0040438F E8C4FFFFFF call @NewAnsiString

这里调用了NewAnsiString来生成一个新的标准字符串。
我们跳过来顺道看看string到底怎么生成的。string生成尽管是自动的,
其实和手工声称PChar差不多,不同的是比PChar多的长度位和引用计数的初始化。

@NewAnsiString
00404358 85C0 test eax,eax
0040435A 7E24 jle +$24
0040435C 50 push eax
0040435D 83C00A add eax,$0a
00404360 83E0FE and eax,-$02
00404363 50 push eax
00404364 E8B3E3FFFF call @GetMem //得到字符串长度后申请内存。
00404369 5A pop edx
0040436A 66C74402FE0000 mov word ptr [edx+eax-$02],$0000
00404371 83C008 add eax,$08
00404374 5A pop edx
00404375 8950FC mov [eax-$04],edx //这里应该就长度位
00404378 C740F801000000 mov [eax-$08],$00000001 //这里是引用记数位
0040437F C3 ret
00404380 31C0 xor eax,eax
00404382 C3 ret
00404383 90 nop

继续LStrFromPCharLen下面的代码。
00404394 89F9 mov ecx,edi
00404396 89C7 mov edi,eax
00404398 85F6 test esi,esi
0040439A 7409 jz +$09
0040439C 89C2 mov edx,eax
0040439E 89F0 mov eax,esi
004043A0 E873E5FFFF call Move //然后把这个新的字符串的数据移到需要的字符串。
004043A5 89D8 mov eax,ebx
004043A7 E8E8FEFFFF call @LStrClr //清理NewAnsiString生成的临时字符串
004043AC 893B mov [ebx],edi
004043AE 5F pop edi
004043AF 5E pop esi
004043B0 5B pop ebx
004043B1 C3 ret

ShowMessage直接输入string就是直接调用。
ShowMessage(S);
0045B159 8B45FC mov eax,[ebp-$04]
0045B15C E8638EFDFF call ShowMessage

而输入PChar,嗬嗬,又调用了上面说到的LStrFromPChar然后才是调用ShowMessage,
其他的也是如此,如果输入参数没有转换,而是输入了PChar,那编译器会这么处理。
ShowMessage(P);
0045B161 8D45F8 lea eax,[ebp-$08]
0045B164 8BD3 mov edx,ebx
0045B166 E81993FAFF call @LStrFromPChar
0045B16B 8B45F8 mov eax,[ebp-$08]
0045B16E E8518EFDFF call ShowMessage

最后一句:ShowMessage(IntToStr(SizeOf(S)));
如果没有差错的话,就是4,一个指针的长度。

好了,一切都清晰了。
S := S;
使用了LStrLAsg内建函数来赋值。

P := P;
地址赋值。

P := PChar(S);
调用LStrToPChar转换为PChar之后地址赋值。

S := P;

这个最最麻烦,
调用LStrFromPChar来转化。
代码中首先取得PChar的长度,
然后调用LStrFromPCharLen用string的长度赋值,
代码中首先使用NewAnsiString在堆中生成一个临时字符串,
其次调用Move,Copy到目的字符串,
最后清理临时字符串。

好了,我又一次胡诌完毕了。请大家执正!

这是我写的,leeon你看看怎么样,我自己总结的:

关于Delphi 中 String (又叫 LongString、AnsiString) 与传统的 PChar的区别
1. string 是 Delphi 编译器内在支持的(predefined or built-in),是Delphi 的一个基本数据类型,而 PChar 只是一个指向零终止字符串的指针;
2. String 所存字符串是在堆(Heap)分配内存的,String 变量实际上是指向零终止字符串的指针,与此同时它还具有引用计数(reference count)功能,并且自身保存字符串长度,当引用计数为零时,自动释放所占用的空间。
3.将 string 赋值给另一个string,只是一个简单的指针赋值,不产生 copy 动作,只是增加string的引用计数;
4. 将一个 PChar 变量类型赋值给一个 string 变量类型会产生真正的 Copy 动作,即将 PChar 所指向的字符串整个copy到为string分配的内存中;
5. 将 string 赋值给一个 PChar 变量类型,只是简单地将string的指针值赋值给PChar 变量类型,而string 的引用计数并不因此操作而发生变化,因为这种情况 PChar 会对 string 产生依赖,当string 的引用计数为零自动释放内存空间后,PChar很可能指向一个无效的内存地址,在你的程序你必须小心对付这种情况。
6. 对 PChar 的操作速度要远远高于对 string 操作的速度,但 PChar 是一种落后的管理字符串的方式,而 string 则以高效的管理而胜出,PChar 它的存在只是为了兼容早期的类型和操作系统(调用 Windows API时会经常用到),建议平常使用 string。

你的那个demo程序我认为设计得非常糟糕,基本上说不清问题:

我设计的Demo程序:
procedure TForm1.Button1Click(Sender: TObject);
var
P: PChar;
S1, S2: string;
begin
S1 := '1234567ABCDE';
S2 := S1;
P := PChar(S1);
ShowMessage(IntToStr(SizeOf(S1))); //获得 S1 变量的大小
ShowMessage(IntToStr(integer(S1))); //获得 S1 指向的字符串的指针地址
ShowMessage(IntToStr(integer(P))); //获得 P 指向的字符串的指针地址
ShowMessage(IntToStr(integer(S2))); //获得 S2 指向的字符串的指针地址
S1 := P;
ShowMessage(IntToStr(integer(S1)));
ShowMessage(IntToStr(integer(P)));
ShowMessage(IntToStr(integer(S2)));
end;

分析代码要点:
S2 := S1 验证我总结的第3点
P := PChar(S1) 验证我总结的第5点
S1 := P 验证我总结的第4点

我这里按顺序显示的结果(您的结果可能与此不同,但不妨碍分析)
4 <= S1 变量的大小
4530660 <= S1 指向的字符串的指针地址
4530660 <= P 指向的字符串的指针地址
4530660 <= S2 指向的字符串的指针地址
9780484 <= S1 指向的字符串的指针地址 (地址发生变化)
4530660 <= P 指向的字符串的指针地址
4530660 <= S2 指向的字符串的指针地址

关于分析它的汇编代码,我认为没什么必要,先搞清楚这些基本的操作!

dREAMtHEATER
2004.02.2

多线程编程(转)

摘自:http://ggggntqk.blog.163.com/blog/static/4100680200610710221922/
 
多线程编程

2-1 多线程的基本概念

WIN 98/NT/2000/XP 是个多任务操作系统,也就是:一个进程可以划
分为多个线程,每个线程轮流占用CPU 运行时间和资源,或者说,把CPU
时间划成片,每个片分给不同的线程,这样,每个线程轮流的“挂起”和
“唤醒”,由于时间片很小,给人的感觉是同时运行的。
多线程带来如下好处:(自己阅读)
1)避免瓶颈;
2)并行操作;
3)提高效率;
在多线程中,通过优先级管理,可以使重要的程序优先操作,提高了
任务管理的灵活性。
另一方面,在多CPU 系统中,可以把不同的线程在不同的CPU 中执行,
真正做到同时处理多任务(Win 98 只是模拟的,而Win/NT/2000是真正的
多CPU同时操作)。

多线程的两个概念:

1) 进程:也称任务,程序载入内存,并分配资源,称为“一个进程
”。注意:进程本身并不一定要正在执行。进程由以下几部分组成:
a>一个私有的地址空间,它是进程可以使用的一组虚拟内存地址空间;
b>程序的相关代码、数据源;
c>系统资源,比如操作系统同步对象等;
d>至少包含一个线程(主线程);

2) 线程:是程序的执行单位(线程本身并不包括程序代码,真正拥
有代码的是进程),每个进程至少包括一个线程,称为主线程,一个进程
如果有多个线程,就可以共享同一进程的资源,并可以并发执行。
线程是进程的一个执行单元,是操作系统分配CPU 时间的基本实体,
线程主要由如下两部分组成:
a>数据结构;
b>CPU 寄存器和堆栈;
一个进程中的线程,可以独立运行,也可以控制另一个线程的运行。

请注意:
多线程不能滥用,书上提到了多线程的几个缺点(自阅)。

2-2 Tthread 对象

虽然Windows 提供了比较多的多线程设计的API 函数,但是直接使用
API 函数一方面极其不方便,而且使用不当还容易出错。为解决这个问题,
Borland 公司率先推出了一种Tthread 对象,来解决多线程设计上的困难,
简化了多线程问题的处理。
应该注意,Tthread 对象是没有实例的,它和界面的交流,主要依靠
主窗体(主VCL线程),这和其他对象使用上有些区别。

一、Tthread 对象的主要方法

构造线程:

constructor Create(CreateSuspended:boolean)

其中:CreateSuspended=true 构造但不唤醒
false 构造的同时即唤醒

也可以用如下方法

inheried Create(CreateSuspended:boolean)


挂起线程:

suspend

(把线程挂起的次数加一)

唤醒线程:

resume

(注意:注意这个属性是把线程挂起的次数减一,当次数为0 时,
即唤醒。也就是说,线程挂起多少次,唤醒也需要多少次。同时挂起
的时候将保持线程的地址指针不变,所以线程挂起后再唤醒,将从挂
起的地方开始运行)

析构(清除线程所占用的内存):

destroy

终止线程(后面会具体讨论):

Terminate

二、线程应用的简单例子:

下面通过一个例子说明上述方法的应用。我们知道,循环是独占
性最强的运行方式之一,现在希望建立两个线程对象,实现循环的并
行运行。具体方法如下:

File---New---Thread Object

这就自动在主Form中建立了一个线程单元(在对话框里写上线程名
字),默认的名字是Unit2。同样方法建立第二个线程单元Unit3。

要注意的是:Unit2和Unit3中有一个给定的过程:

procedure Object.Execute;
begin

end;

其中的程序是线程唤醒后自动执行的程序,也可以在里面调用其
他自定义的过程和函数。这个过程的结束,意味着线程程序的结束。
为了构造线程,在interface的Type区,定义一个构造过程:

type
Object = class(TThread) //自动给出的,也可以直接改

private

protected

procedure Execute; override;

public
constructor create; //自己写的

并且在implementation区域写上:

constructor Object.create;
begin
inherited create(true);
end

其中Object 为线程对象的名字。所以这么写,是希望在主Form中
调用这个构造过程。
Create()的参数用True,表明构造出的线程为挂起状态。
注意一下,在同一个线程对象里,如果两次构造,将产生两个独立
的线程,不但运行是独立的,而且使用线程的局部变量也是独立的。但
这里为了简化问题,还是建立了两个独立的线程对象,而且两个循环数
是不同的,在并行运算时容易判断出是两个不同的程序在运行。

假定我们给两个线程对象起的名字是:

mymath1
mymath2

这样在Unit1,应该作如下声明:

implementation

{$R *.DFM}

uses unit2,unit3;

var thread1:mymath1;
thread2:mymath2;

这样在主线程,将可以通过这两个线程变量调用对应的线程方法。

在主线程区构造线程的方法是:

thread1:=mymath1.create;
thread2:=mymath2.create;

挂起:

thread1.suspend;
thread2.suspend;

唤醒:

thread1.resume;
thread2.resume;

析构:

thread1.destroy;
thread2.destroy;

这里需要说明的是,由于线程单元需要调用Form的Edit控件(对象),
可以采用两种方法:

1) 在线程单元定义一个TEdit对象,例如

edit4:Tedit;

在Execute过程内直接引用

但在Unit1中一定要在FormCreate过程里作一个赋值:

procedure TForm1.FormCreate(Sender: TObject);
begin
thread1.edit4:=edit1;
end;

这样,就把第一线程的edit4与Form上的edit1联系来。

2)在第二个线程中首先声明调用Unti1,也就是要加上

Uses Unit1;

这样就可以在该线程单元直接调用主Form的控件了,比如在Unit3中
可以写:

form1.edit2.text:=inttostr(i)

了解了这些基本规则,就可以写出比较复杂的多线程程序了。
还有一点要说明的,默认生成的线程单元,调用的单元只有一个:

Uses Classes;

这样,往往很多函数和对象在线程单元里不能使用,所以在必要时,
应该根据需要User相应的单元,这个例程为了简单,把大部分常用的单
元都拷过去了,这并不是推荐的办法,因为这样一来会使程序的垃圾过
多,所以,一般要用什么拷什么。

 三、常用的API 函数

在处理多线程问题的时候,也经常用到Windows提供的API 函数,需
要说明的是,Tthread 对象内部封装的方法,其实主要也是调用API 函
数,但是,考虑更全面,更安全。而直接调用API 函数,往往会因为运
用不当,出现一些不应有的错误。所以,我个人以为,只要用Tthread
对象的方法能解决的,就不要直接调用API 函数,API 函数只应该在用
在Tthread 对象方法解决不了的时候。
例如Tthread 对象方法内部调用API 函数的时候,一般使用推荐的
默认值,但需要更精细的控制时,就可以直接使用API 函数。
其实,Tthread 对象方法已经受到了大多数程序设计者的认可,比
如,原来VB是不具备直接处理多线程的能力的,但是,现在VB.Net就宣
称,它具备了简单处理多线程问题的能力,这就很说明问题。
下面简单介绍几种API 函数,为了清晰方便,这里着重在于说明,
函数正确的描述可以自己阅读书上的例子和手册:
构建线程:

CreateThread(参数1,--安全属性(一般=Nil,默认安全属性)

参数2,--线程堆栈尺寸(一般=0,与主线程相同长
度,而且可以根据需要自动变化)
参数3,--指向函数名指针,@函数名,这个参数十
分重要,不正确将无法调用成功。
参数4,--用户需要向线程传递的参数,是一个指向
结构的指针,不需传递参数时,为Nil。
参数5)--传入与线程有关的一些参数,例如:
CREATE_SUSPENDED 创建一个挂起的线程;
0 创建后立即激活。

书上有这个函数应用的十分清晰的例子,可以自己阅读。
一般并不推荐使用 CreateTheard函数,而推荐使用RTL 库里的
System单元中定义的 BeginTheard函数,因为这除了能创建一个线程
和一个入口函数以外,还增加了几项保护措施,具体的请参阅书上的
第10页说明。

对应suspend(挂起)和resume(唤醒)的两个API 函数为:

Function SuspendThread(hThread:Thandle):DWORD;

Function ResumeThread(hThread:Thandle):DWORD;

其中,Thandle被要求控制线程的句柄,函数调用成功,返回挂
起的次数,调用不成功。则返回0xFFFFFFFF。

四、线程的终止和退出:

1)自动退出:

一个线程从Execute()过程中退出,即意味着线程的终止,此时
将调用Windows的ExitThread()函数来清除线程所占用的堆栈。
如果线程对象的 FreeOnTerminate 属性设为True,则线程对象
将自动删除,并释放线程所占用的资源。
这是消除线程对象最简单的办法。

2)受控退出:

利用线程对象的Terminate属性,可以由进程或者由其他线程控
制线程的退出。只需要简单的调用该线程的Terminate方法,并设直
线程对象的Terminate属性为True。
在线程中,应该不断监视Terminate的值,一旦发现为True,则
退出,例如在Execute()过程中可以这样写:

While not Terminate do
begin
........
end;

3)退出的API 函数:

关于线程退出的API 函数声明如下:code

Function TerminateThread(hThread:Thandle;dwExitCode:DWORD);

不过,这个函数会使代码立刻终止,而不管程序中有没有

try....finally

机制,可能会导致错误,不到万不得已,最好不要使用。

4) 利用挂起线程的方法(suspend)

利用挂起线程的suspend方法,后面跟个Free,也可以释放线程,
例如:

thread1.suspend; //挂起
thread2.free; //释放

书上有相应的例子。


五、 线程的优先级:

在多线程的情况下,一般要根据线程执行任务的重要性,给线程
适当的优先级,一般如果量的线程同时申请CPU 时间,优先级高的线
程优先。

在Windows下,给线程的优先级分为30级,而Delphi中Tthread
对象相对简单的把优先级分为七级。也就是在Tthread中声明了一个
枚举类型TTthreadPriority:

type

TTthreadPriority(tpidle,tpLowest,tpLower,tpNormal,
tpHight,tpHighest,tpTimecrital)

分别对应的是最低(系统空闲时有效,-15),较低(-2),低
(-1),正常(普通0),高(1),较高(2),最高(15)。

其中tpidle和tpTimecrital有些特殊,具体情况请阅读书上有
关内容。

设置优先级可使用thread对象的priority属性:

threadObject.priority:=Tthreadpriority(级别);

这里给出了一个演示多线程优先级的实例:


2-3 在数据库中使用多线程

一)使用ADO模式

由于Delphi 6.0的ADO 数据源控件内置了多线程能力,所以,在
ADO模式下,使用多线程不需要做更多的工作。用两个ADOTable控件,
分别连到两个数据库,并且分别通过DataSource控件,与数据帮定控
件联系就可以了,这样就可以实现前后台处理数据库问题。


二)使用BDE模式和Tseeion对象

如果需要使用BDE 模式,那么多线程使用数据库,就要考虑Session
的问题。在单线程时,每个数据源的建立就自动生成一个Session,
这是这个数据源私有的关于数据库信息的文件。但多线程时,必须统
一管理,所以在BDE 中专门提供了一个Tsession对象,它可以同时管理
不同的Databas数据源对象。
Databas数据源可以接受来自不同数据平台的数据库。

数据库1---databas(2)----table(Qurey)(3)---datasource
| |
| |
|--------- Tsession(1)
| |
| |
数据库2---databas(2)----table(Qurey)(3)---datasource


方法:
1)Tsession
属性:SessionName=名(自起)
Active=true (激活)
2)Database(可以有多个)
属性:SessionName=Tsession名
Dataname=名(自起,作为Table的标识)
AliasName=数据库别名
Connected=True (激活)
3)Table或Qurey
属性:SessionName=Tsession名(不要用默认值)
DatabaseName=如果前面起了名,这里就会出现Database
的名字。
Tablename=表名
Active=true (激活)
以后比如加入Datasoucre和其他一样,这样就可以构造两个前后
台处理的数据库管理系统了。

2-4 多线程的同步机制

同步机制,实际上是事件驱动机制,意思是让线程平时处于“休
眠”状态,除非发生某个事件才触发。
例如一个拷贝文件,拷贝线程完成一个程序块后,再唤醒进程条
线程做一个格的填充。
研究多线程的同步机制的必要性在于,多线程同步工作时,如果
同时调用相同的资源,就可能会出现问题,一般读出是不会有问题的,
但是,如果写入(全局变量、数据库),就会发生冲突,甚至产生死
锁和竞争问题。

一、使用Synchronize方法

这个方法用于访问VCL 主线程所管理的资源,其方法的应用是:
第一步:把访问主窗口(或主窗口控件资源)的代码放到线程的
一个方法中;
第二步:是在线程对象的Execute方法中,通过Synchronize方法
使用该方法。
实例:
procedure Theater.Execute;
begin
Synchronize(update);
end;

procedure Theater.update;
begin
.........
end;

这里通过 Synchronize使线程方法update同步。


二、使用VCL类的Look方法

在Delphi的IDE提供的构件中,有一些对象内部提供了线程的同
步机制,工作线程可以直接使用这些控件,比如:Tfont,Tpen,
TBitmap,TMetafile,Ticon等。另外,一个很重要的控件对象叫
TCanvas,提供了一个Lock方法用于线程的同步,当一个线程使用此
控件对象的时候,首先调用这个对象的Lock方法,然后对这个控件
进行操作,完毕后再调用Unlock方法,释放对控间的控制权。
例如:
CanversObject.look;
try
画图
finally
CanversObject.unlock;
end;
{使用这个保护机制,保证不论有没有异常,unlock都会被执行
否则很可能会发生死锁。在多线程设计的时候,应该很注意发生死
锁的问题}

三、Waitfor方法
当一个线程应该等待另一个线程结束时,可以调用Waitfor方法。
这个方法属于等待线程对象,Waitfor方法的原型如下:

Function Waitfor(Const Astring:string):string;

比如在前面最基本的线程的例子中,唤醒线程的语句中加上

thread1.resume;
thread1.waitfor;
thread2.resume;

那么所有的线程都必须等待thread1运行完毕后才能运行,其中
包括主线程,可以预想,由于thread1调用了主窗体的Edit控件,那
么,在thread1运行中间,Edie1也不会显示。
这就告诉我们,这样的代码是不能作为主线程的一部分的,如果
与主窗体连接的线程内等待另一个线程结束,而另一个线程又要等待
访问用户界面,就可能是程序陷于死锁。
这点在应用的时候要谨慎。


四、利用Windows的API 实现同步

Windows API函数提供了很多同步技术,下面简要介绍。

1)临界区

使用线程的时候,遇到的一个基本的问题,就是多个线程访问同
一个对象,比如访问相同的文件、DLL、相同的通讯资源,特别是数
据库的访问,当多个线程对同一数据库字段写入的时候,其结果会出
现不确定性。
临界区用于解决这个问题,它可以保证线程使用敏感数据的时候,
阻赛其他的线程访问名干数据,使用时首先要初始化,其声明一个
TRTLCriticalSection类型的变量:

var
CS:TRTLCriticalSection;

初始化:

initializeCriticalSection(cs);

独占

EnterCriticalSection(cs);

解除独占

LeaveCriticalSection(CS);

使用临界区是比较方便而且概念比较清晰的的线程同步机制,应用比
较广泛。
请注意,临界区只能在一个进程内使用,首先要标记出把数据作为临
界区操作的那些代码,在这部分代码执行前,计算机首先要查看一下全局
记录,已确定是否有其它线程在临界区中,同时也要查看这个临界区是否
和第一个临界区相关,也就是说同一个程序中可能会有几个不同的临界区,
然后计算机再决定运行策略。
下面简单的实例说明临界区的用法:

《以下略》
2)互斥对象

3)信号量

delphi多线程(转)

摘自:http://blog.csdn.net/jqandjq/archive/2010/06/01/5638821.aspx

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;

  ThreadFunc:线程函数,

  BeginThread:线程启动函数。

    这三个函数的功能是基本相同的,它们都是将线程函数中的代码放到一个独立的线程中执行。线程函数与一般函数的最大不同在于,线程函数一开始执行,这三个线 程启动函数就返回了,主线程继续向下执行,而线程函数在一个独立的线程中执行,它要执行多久,什么时候返回,主线程是不管也不知道的。
    正常情况下,线程函数返回后,线程就终止了。但也有其它方式:
//以下3个函数能主动终止线程,而使用TThread类则没法在线程的执行过程中终止线程
Windows API:
VOID ExitThread( DWORD dwExitCode );
C Runtime Library:
void _endthread(void);
Delphi Runtime Library:
procedure EndThread(ExitCode: Integer);

    为了记录一些必要的线程数据(状态/属性等),OS会为线程创建一个内部Object,如在Windows中那个Handle便是这个内部Object的Handle,所以在线程结束的时候还应该释放这个Object。

    虽然说用API或RTL(Runtime Library)已经可以很方便地进行多线程编程了,但是还是需要进行较多的细节处理,为此Delphi在Classes单元中对线程作了一个较好的封装,这就是VCL的线程类:TThread
    使用这个类也很简单,大多数的Delphi书籍都有说,基本用法是:先从TThread派生一个自己的线程类(因为TThread是一个抽象类,不能生成 实例),然后是Override抽象方法:Execute(这就是线程函数,也就是在线程中执行的代码部分),如果需要用到可视VCL对象,还需要通过 Synchronize过程来执行。关于之方面的具体细节,这里不再赘述,请参考相关书籍。
    本文接下来要讨论的是TThread类是如何对线程进行封装的,也就是深入研究一下TThread类的实现。因为只有是真正地了解了它,才更好地使用它。
    下面是DELPHI7中TThread类的声明(本文只讨论在Windows平台下的实现,所以去掉了所有有关Linux平台部分的代码):

  TThread = class

  private
    FHandle: THandle;
    FThreadID: THandle;
    FCreateSuspended: Boolean;
    FTerminated: Boolean;
    FSuspended: Boolean;
    FFreeOnTerminate: Boolean;
    FFinished: Boolean;
    FReturnValue: Integer;
    FOnTerminate: TNotifyEvent;
    FSynchronize: TSynchronizeRecord;//delphi help中找不到
    FFatalException: TObject;
    procedure CallOnTerminate;//调用在OnTerminate编写的代码
    class procedure Synchronize(ASyncRec: PSynchronizeRecord); overload;
    function GetPriority: TThreadPriority;
    procedure SetPriority(Value: TThreadPriority);
    procedure SetSuspended(Value: Boolean);

  protected
    procedure CheckThreadError(ErrCode: Integer); overload;
    procedure CheckThreadError(Success: Boolean); overload;
    procedure DoTerminate; virtual;//将会调用 CallOnTerminate
    procedure Execute; virtual; abstract;
    procedure Synchronize(Method: TThreadMethod); overload;
    property ReturnValue: Integer read FReturnValue write FReturnValue;
    property Terminated: Boolean read FTerminated;

  public
    constructor Create(CreateSuspended: Boolean);
    destructor Destroy; override;
    procedure AfterConstruction; override;
    procedure Resume;
    procedure Suspend;
    procedure Terminate;
    function WaitFor: LongWord;
    class procedure Synchronize(AThread: TThread; AMethod: TThreadMethod); overload;
    class procedure StaticSynchronize(AThread: TThread; AMethod: TThreadMethod);
    property FatalException: TObject read FFatalException;
    property FreeOnTerminate: Boolean read FFreeOnTerminate write FFreeOnTerminate;
    property Handle: THandle read FHandle;
    property Priority: TThreadPriority read GetPriority write SetPriority;
    property Suspended: Boolean read FSuspended write SetSuspended;
    property ThreadID: THandle read FThreadID;
    property OnTerminate: TNotifyEvent read FOnTerminate write FOnTerminate;
  end;

    TThread类在Delphi的RTL里算是比较简单的类,类成员也不多,类属性都很简单明白,本文将只对几个比较重要的类成员方法和唯一的事件:OnTerminate作详细分析。
    首先就是构造函数:

constructor TThread.Create(CreateSuspended: Boolean);
begin
  inherited Create;
  AddThread;
  FSuspended := CreateSuspended;
  FCreateSuspended := CreateSuspended;
  FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID);//BeginThread创建挂起线程,在AfterConstruction过程中才根据FCreateSuspended 确定是否执行
  if FHandle = 0 then
    raise EThread.CreateResFmt(@SThreadCreateError, [SysErrorMessage(GetLastError)]);

end;

    虽然这个构造函数没有多少代码,但却可以算是最重要的一个成员,因为线程就是在这里被创建的。
在通过Inherited调用TObject.Create后,第一句就是调用一个过程:AddThread,其源码如下:

procedure AddThread;
begin
  InterlockedIncrement(ThreadCount);  //此为kernel32内核函数的调用
end;

    同样有一个对应的RemoveThread:
procedure RemoveThread;
begin
  InterlockedDecrement(ThreadCount);
end;

ThreadCount为全却变量

    它们的功能很简单,就是通过增减一个全局变量来统计进程中的线程数。只是这里用于增减变量的并不是常用的Inc/Dec过程,而是用了 InterlockedIncrement/InterlockedDecrement这一对过程,它们实现的功能完全一样,都是对变量加一或减一。但它 们有一个最大的区别,那就是InterlockedIncrement/InterlockedDecrement是线程安全的。即它们在多线程下能保证 执行结果正确,而Inc/Dec不能。或者按操作系统理论中的术语来说,这是一对“原语”操作。
    以加一为例来说明二者实现细节上的不同:
    一般来说,对内存数据加一的操作分解以后有三个步骤:
    1、从内存中读出数据
    2、数据加一
    3、存入内存
    现在假设在一个两个线程的应用中用Inc进行加一操作可能出现的一种情况:
    1、线程A从内存中读出数据(假设为3) 
    2、线程B从内存中读出数据(也是3)
    3、线程A对数据加一(现在是4)
    4、线程B对数据加一(现在也是4)
    5、线程A将数据存入内存(现在内存中的数据是4)
    6、线程B也将数据存入内存(现在内存中的数据还是4,但两个线程都对它加了一,应该是5才对,所以这里出现了错误的结果)
    而用InterlockIncrement过程则没有这个问题,因为所谓“原语”是一种不可中断的操作,即操作系统能保证在一个“原语”执行完毕前不会进 行线程切换。所以在上面那个例子中,只有当线程A执行完将数据存入内存后,线程B才可以开始从中取数并进行加一操作,这样就保证了即使是在多线程情况下, 结果也一定会是正确的。
    前面那个例子也说明一种“线程访问冲突”的情况,这也就是为什么线程之间需要“同步”(Synchronize),关于这个,在后面说到同步时还会再详细讨论。
    说到同步,有一个题外话:加拿大滑铁卢大学的教授李明曾就Synchronize一词在“线程同步”中被译作“同步”提出过异议,个人认为他说的其实很有 道理。在中文中“同步”的意思是“同时发生”,而“线程同步”目的就是避免这种“同时发生”的事情。而在英文中,Synchronize的意思有两个:一 个是传统意义上的同步(To occur at the same time),另一个是“协调一致”(To operate in unison)。在“线程同步”中的Synchronize一词应该是指后面一种意思,即“保证多个线程在访问同一数据时,保持协调一致,避免出错”。不 过像这样译得不准的词在IT业还有很多,既然已经是约定俗成了,本文也将继续沿用,只是在这里说明一下,因为软件开发是一项细致的工作,该弄清楚的,绝不 能含糊。
    扯远了,回到TThread的构造函数上,接下来最重要就是这句了:

FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID);

    这里就用到了前面说到的Delphi RTL函数BeginThread,它有很多参数,关键的是第三、四两个参数。第三个参数就是前面说到的线程函数,即在线程中执行的代码部分。第四个参数 则是传递给线程函数的参数,在这里就是创建的线程对象(即Self)。其它的参数中,第五个是用于设置线程在创建后即挂起,不立即执行(启动线程的工作是 在AfterConstruction中根据CreateSuspended标志来决定的),第六个是返回线程ID。
    现在来看TThread的核心:线程函数ThreadProc。有意思的是这个线程类的核心却不是线程的成员,而是一个全局函数(因为BeginThread过程的参数约定只能用全局函数)。下面是它的代码:

ThreadProc为全局函数

function ThreadProc(Thread: TThread): Integer;
var
  FreeThread: Boolean;
begin
  try
    if not Thread.Terminated then
    try
      Thread.Execute;
    except
      Thread.FFatalException := AcquireExceptionObject;
    end;
  finally
    FreeThread := Thread.FFreeOnTerminate;
    Result := Thread.FReturnValue;
    Thread.DoTerminate;//标识FTerminated,表示线程已经终止运行
    Thread.FFinished := True;//标识线程代码已经执行完毕
    SignalSyncEvent;
    if FreeThread then Thread.Free;
    EndThread(Result);//使用RTL函数终止线程
  end;
end;

    虽然也没有多少代码,但却是整个TThread中最重要的部分,因为这段代码是真正在线程中执行的代码。下面对代码作逐行说明:
    首先判断线程类的Terminated标志,如果未被标志为终止,则调用线程类的Execute方法执行线程代码,因为TThread是抽象类,Execute方法是抽象方法,所以本质上是执行派生类中的Execute代码。
    所以说,Execute就是线程类中的线程函数,所有在Execute中的代码都需要当作线程代码来考虑,如防止访问冲突等。
    如果Execute发生异常,则通过AcquireExceptionObject取得异常对象,并存入线程类的FFatalException成员中。
    最后是线程结束前做的一些收尾工作。局部变量FreeThread记录了线程类的FreeOnTerminated属性的设置,然后将线程返回值设置为线程类的返回值属性的值。然后执行线程类的DoTerminate方法。
DoTerminate方法的代码如下:

procedure TThread.DoTerminate;
begin
  if Assigned(FOnTerminate) then Synchronize(CallOnTerminate);
end;

    很简单,就是通过Synchronize来调用CallOnTerminate方法,而CallOnTerminate方法的代码如下,就是简单地调用OnTerminate事件:

procedure TThread.CallOnTerminate;
begin
  if Assigned(FOnTerminate) then FOnTerminate(Self);
end;

    因为OnTerminate事件是在Synchronize中执行的,所以本质上它并不是线程代码,而是主线程代码(具体见后面对Synchronize的分析)。
    执行完OnTerminate后,将线程类的FFinished标志设置为True。
    接下来执行SignalSyncEvent过程,其代码如下:

procedure SignalSyncEvent;
begin
  SetEvent(SyncEvent);\\SetEvent也是kernel32函数
end;
SyncEvent是全局变量

{ SyncEvent is an Event handle that is signaled every time a thread wishes to
  synchronize with the main thread or is terminating.  This handle us suitable
  for use with WaitForMultipleObjects.  When this object is signaled,
  CheckSynchronize *must* be called in order to reset the event.  Do not call
  ResetEvent on this handle, or background threads may hang waiting for
  Synchronize to return.
}


    也很简单,就是设置一下一个全局Event:SyncEvent,关于Event的使用,本文将在后文详述,而SyncEvent的用途将在WaitFor过程中说明。
    然后根据FreeThread中保存的FreeOnTerminate设置决定是否释放线程类,在线程类释放时,还有一些操作,详见接下来的析构函数实现。
    最后调用EndThread结束线程,返回线程返回值。
    至此,线程完全结束。
    说完构造函数,再来看析构函数:

destructor TThread.Destroy;
begin
  if (FThreadID <> 0) and not FFinished then
  begin
    Terminate;//只是简单地设置线程类的Terminated标志
    if FCreateSuspended then
      Resume;
    WaitFor;//其功能就是等待到线程结束后才继续向下执行
  end;
  if FHandle <> 0 then CloseHandle(FHandle);
  inherited Destroy;
  FFatalException.Free;
  RemoveThread;
end;

    在线程对象被释放前,首先要检查线程是否还在执行中,如果线程还在执行中(线程ID不为0,并且线程结束标志未设置),则调用Terminate过程结束线程。Terminate过程只是简单地设置线程类的Terminated标志,如下面的代码:

procedure TThread.Terminate;
begin
  FTerminated := True;
end;

    所以线程仍然必须继续执行到正常结束后才行,而不是立即终止线程,这一点要注意。
    在这里说一点题外话:很多人都问过我,如何才能“立即”终止线程(当然是指用TThread创建的线程)。结果当然是不行!终止线程的唯一办法就是让 Execute方法执行完毕,所以一般来说,要让你的线程能够尽快终止,必须在Execute方法中在较短的时间内不断地检查Terminated标志, 以便能及时地退出。这是设计线程代码的一个很重要的原则!
    当然如果你一定要能“立即”退出线程,那么TThread类不是一个好的选择,因为如果用API强制终止线程的话,最终会导致TThread线程对象不能 被正确释放,在对象析构时出现Access Violation。这种情况你只能用API或RTL函数来创建线程。
    如果线程处于启动挂起状态,则将线程转入运行状态,然后调用WaitFor进行等待,其功能就是等待到线程结束后才继续向下执行。关于WaitFor的实现,将放到后面说明。
    线程结束后,关闭线程Handle(正常线程创建的情况下Handle都是存在的),释放操作系统创建的线程对象。
    然后调用TObject.Destroy释放本对象,并释放已经捕获的异常对象,最后调用RemoveThread减小进程的线程数。
    其它关于Suspend/Resume及线程优先级设置等方面,不是本文的重点,不再赘述。下面要讨论的是本文的另两个重点:Synchronize和WaitFor。
    但是在介绍这两个函数之前,需要先介绍另外两个线程同步技术:事件和临界区。
    事件(Event,是操作系统为实现多线程而设立的相关机制)与Delphi中的事件有所不同。从本质上说,Event其实相当于一个全局的布尔变量。它 有两个赋值操作:Set和Reset,相当于把它设置为True或False。而检查它的值是通过WaitFor操作进行。对应在Windows平台上, 是三个API函数:SetEvent、ResetEvent、WaitForSingleObject(实现WaitFor功能的API还有几个,这是最 简单的一个)。
    这三个都是原语,所以Event可以实现一般布尔变量不能实现的在多线程中的应用。Set和Reset的功能前面已经说过了,现在来说一下


WaitFor的功能:
    WaitFor的功能是检查Event(即全局SyncEvent)的状态是否是Set状态(相当于True),如果是则立即返回,如果不是,则等待它变 为Set状态,在等待期间,调用WaitFor的线程处于挂起状态。另外WaitFor有一个参数用于超时设置,如果此参数为0,则不等待,立即返回 Event的状态,如果是INFINITE则无限等待,直到Set状态发生,若是一个有限的数值,则等待相应的毫秒数后返回Event的状态。
    当Event从Reset状态向Set状态转换时,唤醒其它由于WaitFor这个Event而挂起的线程,这就是它为什么叫Event的原因。所谓“事件”就是指“状态的转换”。通过Event可以在线程间传递这种“状态转换”信息。
    当然用一个受保护(见下面的临界区介绍)的布尔变量也能实现类似的功能,只要用一个循环检查此布尔值的代码来代替WaitFor即可。从功能上说完全没有 问题,但实际使用中就会发现,这样的等待会占用大量的CPU资源,降低系统性能,影响到别的线程的执行速度,所以是不经济的,有的时候甚至可能会有问题。 所以不建议这样用。
    临界区(CriticalSection)则是一项共享数据访问保护的技术。它其实也是相当于一个全局的布尔变量。但对它的操作有所不同,它只有两个操 作:Enter和Leave,同样可以把它的两个状态当作True和False,分别表示现在是否处于临界区中。这两个操作也是原语,所以它可以用于在多 线程应用中保护共享数据,防止访问冲突。
    用临界区保护共享数据的方法很简单:在每次要访问共享数据之前调用Enter设置进入临界区标志,然后再操作数据,最后调用Leave离开临界区。它的保 护原理是这样的:当一个线程进入临界区后,如果此时另一个线程也要访问这个数据,则它会在调用Enter时,发现已经有线程进入临界区,然后此线程就会被 挂起,等待当前在临界区的线程调用Leave离开临界区,当另一个线程完成操作,调用Leave离开后,此线程就会被唤醒,并设置临界区标志,开始操作数 据,这样就防止了访问冲突。
    以前面那个InterlockedIncrement为例,我们用CriticalSection(Windows API)来实现它:

Var
  InterlockedCrit : TRTLCriticalSection;
Procedure InterlockedIncrement( var aValue : Integer );
Begin
  EnterCriticalSection( InterlockedCrit );
  Inc( aValue );
  LeaveCriticalSection( InterlockedCrit );
End;

    现在再来看前面那个例子:
    1.线程A进入临界区(假设数据为3)
    2.线程B进入临界区,因为A已经在临界区中,所以B被挂起
    3.线程A对数据加一(现在是4)
    4.线程A离开临界区,唤醒线程B(现在内存中的数据是4)
    5.线程B被唤醒,对数据加一(现在就是5了)
    6.线程B离开临界区,现在的数据就是正确的了。
    临界区就是这样保护共享数据的访问。
    关于临界区的使用,有一点要注意:即数据访问时的异常情况处理。因为如果在数据操作时发生异常,将导致Leave操作没有被执行,结果将使本应被唤醒的线程未被唤醒,可能造成程序的没有响应。所以一般来说,如下面这样使用临界区才是正确的做法:

EnterCriticalSection
Try
   //  操作临界区数据
Finally
  LeaveCriticalSection
End;

    最后要说明的是,Event和CriticalSection都是操作系统资源,使用前都需要创建,使用完后也同样需要释放。如TThread类用到的一 个全局Event:SyncEvent和全局CriticalSection:TheadLock,都是在 InitThreadSynchronization和DoneThreadSynchronization中进行创建和释放的,而它们则是在 Classes单元的Initialization和Finalization中被调用的。
    由于在TThread中都是用API来操作Event和CriticalSection的,所以前面都是以API为例,其实Delphi已经提供了对它们 的封装,在SyncObjs单元中,分别是TEvent类和TCriticalSection类。用法也与前面用API的方法相差无几。因为TEvent 的构造函数参数过多,为了简单起见,Delphi还提供了一个用默认参数初始化的Event类:TSimpleEvent。
    顺便再介绍一下另一个用于线程同步的类:TMultiReadExclusiveWriteSynchronizer,它是在SysUtils单元中定义 的。据我所知,这是Delphi RTL中定义的最长的一个类名,还好它有一个短的别名:TMREWSync。至于它的用处,我想光看名字就可以知道了,我也就不多说了。
    有了前面对Event和CriticalSection的准备知识,可以正式开始讨论Synchronize和WaitFor了。
    我们知道,Synchronize是通过将部分代码放到主线程中执行来实现线程同步的,因为在一个进程中,只有一个主线程。先来看看Synchronize的实现:

procedure TThread.Synchronize(Method: TThreadMethod);
begin
  FSynchronize.FThread := Self;
  FSynchronize.FSynchronizeException := nil;
  FSynchronize.FMethod := Method;
  Synchronize(@FSynchronize);
end;

    其中FSynchronize是一个记录类型:
  PSynchronizeRecord = ^TSynchronizeRecord;
  TSynchronizeRecord = record
    FThread: TObject;
    FMethod: TThreadMethod;
    FSynchronizeException: TObject;
  end;

    用于进行线程和主线程之间进行数据交换,包括传入线程类对象,同步执行的方法及发生的异常。
    在Synchronize中调用了它的一个重载版本,而且这个重载版本比较特别,它是一个“类方法”。所谓类方法,是一种特殊的类成员方法,它的调用并不 需要创建类实例,而是像构造函数那样,通过类名调用。之所以会用类方法来实现它,是因为为了可以在线程对象没有创建时也能调用它。不过实际中是用它的另一 个重载版本(也是类方法)和另一个类方法StaticSynchronize。下面是这个Synchronize的代码:

class procedure TThread.Synchronize(ASyncRec: PSynchronizeRecord);
var
  SyncProc: TSyncProc;
begin
  if GetCurrentThreadID = MainThreadID then
    ASyncRec.FMethod
  else
  begin
    SyncProc.Signal := CreateEvent(nil, True, False, nil);
    try
      EnterCriticalSection(ThreadLock);
      try
        if SyncList = nil then
          SyncList := TList.Create;
        SyncProc.SyncRec := ASyncRec;
        SyncList.Add(@SyncProc);
        SignalSyncEvent;
        if Assigned(WakeMainThread) then
          WakeMainThread(SyncProc.SyncRec.FThread);
        LeaveCriticalSection(ThreadLock);
        try
          WaitForSingleObject(SyncProc.Signal, INFINITE);
        finally
          EnterCriticalSection(ThreadLock);
        end;
      finally
        LeaveCriticalSection(ThreadLock);
      end;
    finally
      CloseHandle(SyncProc.Signal);
    end;
    if Assigned(ASyncRec.FSynchronizeException) then raise ASyncRec.FSynchronizeException;
  end;
end;

    这段代码略多一些,不过也不算太复杂。
    首先是判断当前线程是否是主线程,如果是,则简单地执行同步方法后返回。
    如果不是主线程,则准备开始同步过程。
    通过局部变量SyncProc记录线程交换数据(参数)和一个Event Handle,其记录结构如下:

  TSyncProc = record
    SyncRec: PSynchronizeRecord;
    Signal: THandle;
  end;

    然后创建一个Event,接着进入临界区(通过全局变量ThreadLock进行,因为同时只能有一个线程进入Synchronize状态,所以可以用全 局变量记录),然后就是把这个记录数据存入SyncList这个列表中(如果这个列表不存在的话,则创建它)。可见ThreadLock这个临界区就是为 了保护对SyncList的访问,这一点在后面介绍CheckSynchronize时会再次看到。
    再接下就是调用SignalSyncEvent,其代码在前面介绍TThread的构造函数时已经介绍过了,它的功能就是简单地将SyncEvent作一个Set的操作。关于这个SyncEvent的用途,将在后面介绍WaitFor时再详述。
    接下来就是最主要的部分了:调用WakeMainThread事件进行同步操作。WakeMainThread是一个TNotifyEvent类型的全局 事件。这里之所以要用事件进行处理,是因为Synchronize方法本质上是通过消息,将需要同步的过程放到主线程中执行,如果在一些没有消息循环的应 用中(如Console或DLL)是无法使用的,所以要使用这个事件进行处理。
    而响应这个事件的是Application对象,下面两个方法分别用于设置和清空WakeMainThread事件的响应(来自Forms单元):

procedure TApplication.HookSynchronizeWakeup;
begin
  Classes.WakeMainThread := WakeMainThread;
end;

procedure TApplication.UnhookSynchronizeWakeup;
begin
  Classes.WakeMainThread := nil;
end;

    上面两个方法分别是在TApplication类的构造函数和析构函数中被调用。
    这就是在Application对象中WakeMainThread事件响应的代码,消息就是在这里被发出的,它利用了一个空消息来实现:

procedure TApplication.WakeMainThread(Sender: TObject);
begin
  PostMessage(Handle, WM_NULL, 0, 0);
end;

    而这个消息的响应也是在Application对象中,见下面的代码(删除无关的部分):
procedure TApplication.WndProc(var Message: TMessage);

begin
  try
    …
    with Message do
      case Msg of
        …
        WM_NULL:
          CheckSynchronize;
        …
  except
    HandleException(Self);
  end;
end;
    其中的CheckSynchronize也是定义在Classes单元中的,由于它比较复杂,暂时不详细说明,只要知道它是具体处理Synchronize功能的部分就好,现在继续分析Synchronize的代码。
    在执行完WakeMainThread事件后,就退出临界区,然后调用WaitForSingleObject开始等待在进入临界区前创建的那个 Event。这个Event的功能是等待这个同步方法的执行结束,关于这点,在后面分析CheckSynchronize时会再说明。
    注意在WaitForSingleObject之后又重新进入临界区,但没有做任何事就退出了,似乎没有意义,但这是必须的!
    因为临界区的Enter和Leave必须严格的一一对应。那么是否可以改成这样呢:

        if Assigned(WakeMainThread) then
          WakeMainThread(SyncProc.SyncRec.FThread);
        WaitForSingleObject(SyncProc.Signal, INFINITE);
      finally
        LeaveCriticalSection(ThreadLock);
      end;

    上面的代码和原来的代码最大的区别在于把WaitForSingleObject也纳入临界区的限制中了。看上去没什么影响,还使代码大大简化了,但真的可以吗?
    事实上是不行!
    因为我们知道,在Enter临界区后,如果别的线程要再进入,则会被挂起。而WaitFor方法则会挂起当前线程,直到等待别的线程SetEvent后才 会被唤醒。如果改成上面那样的代码的话,如果那个SetEvent的线程也需要进入临界区的话,死锁(Deadlock)就发生了(关于死锁的理论,请自 行参考操作系统原理方面的资料)。
    死锁是线程同步中最需要注意的方面之一!
    最后释放开始时创建的Event,如果被同步的方法返回异常的话,还会在这里再次抛出异常。
    回到前面CheckSynchronize,见下面的代码:

function CheckSynchronize(Timeout: Integer = 0): Boolean;
var
  SyncProc: PSyncProc;
  LocalSyncList: TList;
begin
  if GetCurrentThreadID <> MainThreadID then
    raise EThread.CreateResFmt(@SCheckSynchronizeError, [GetCurrentThreadID]);
  if Timeout > 0 then
    WaitForSyncEvent(Timeout)
  else
    ResetSyncEvent;
  LocalSyncList := nil;
  EnterCriticalSection(ThreadLock);
  try
    Integer(LocalSyncList) := InterlockedExchange(Integer(SyncList), Integer(LocalSyncList));
    try
      Result := (LocalSyncList <> nil) and (LocalSyncList.Count > 0);
      if Result then
      begin
        while LocalSyncList.Count > 0 do
        begin
          SyncProc := LocalSyncList[0];
          LocalSyncList.Delete(0);
          LeaveCriticalSection(ThreadLock);
          try
            try
              SyncProc.SyncRec.FMethod;
            except
              SyncProc.SyncRec.FSynchronizeException := AcquireExceptionObject;
            end;
          finally
            EnterCriticalSection(ThreadLock);
          end;
          SetEvent(SyncProc.signal);
        end;
      end;
    finally
      LocalSyncList.Free;
    end;
  finally
    LeaveCriticalSection(ThreadLock);
  end;
end;

    首先,这个方法必须在主线程中被调用(如前面通过消息传递到主线程),否则就抛出异常。
    接下来调用ResetSyncEvent(它与前面SetSyncEvent对应的,之所以不考虑WaitForSyncEvent的情况,是因为只有在 Linux版下才会调用带参数的CheckSynchronize,Windows版下都是调用默认参数0的CheckSynchronize)。
    现在可以看出SyncList的用途了:它是用于记录所有未被执行的同步方法的。因为主线程只有一个,而子线程可能有很多个,当多个子线程同时调用同步方法时,主线程可能一时无法处理,所以需要一个列表来记录它们。
    在这里用一个局部变量LocalSyncList来交换SyncList,这里用的也是一个原语:InterlockedExchange。同样,这里也是用临界区将对SyncList的访问保护起来。
    只要LocalSyncList不为空,则通过一个循环来依次处理累积的所有同步方法调用。最后把处理完的LocalSyncList释放掉,退出临界区。
    再来看对同步方法的处理:首先是从列表中移出(取出并从列表中删除)第一个同步方法调用数据。然后退出临界区(原因当然也是为了防止死锁)。
    接着就是真正的调用同步方法了。
    如果同步方法中出现异常,将被捕获后存入同步方法数据记录中。
    重新进入临界区后,调用SetEvent通知调用线程,同步方法执行完成了(详见前面Synchronize中的WaitForSingleObject调用)。
    至此,整个Synchronize的实现介绍完成。
    最后来说一下WaitFor,它的功能就是等待线程执行结束。其代码如下:

function TThread.WaitFor: LongWord;
var
  H: array[0..1] of THandle;
  WaitResult: Cardinal;
  Msg: TMsg;
begin
  H[0] := FHandle;
  if GetCurrentThreadID = MainThreadID then
  begin
    WaitResult := 0;
    H[1] := SyncEvent;
    repeat
      { This prevents a potential deadlock if the background thread
        does a SendMessage to the foreground thread }
      if WaitResult = WAIT_OBJECT_0 + 2 then
        PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE);
      WaitResult := MsgWaitForMultipleObjects(2, H, False, 1000, QS_SENDMESSAGE);
      CheckThreadError(WaitResult <> WAIT_FAILED);
      if WaitResult = WAIT_OBJECT_0 + 1 then
        CheckSynchronize;
    until WaitResult = WAIT_OBJECT_0;
  end else WaitForSingleObject(H[0], INFINITE);
  CheckThreadError(GetExitCodeThread(H[0], Result));
end;

    如果不是在主线程中执行WaitFor的话,很简单,只要调用WaitForSingleObject等待此线程的Handle为Signaled状态即可。
如果是在主线程中执行WaitFor则比较麻烦。首先要在Handle数组中增加一个SyncEvent,然后循环等待,直到线程结束(即MsgWaitForMultipleObjects返回WAIT_OBJECT_0,详见MSDN中关于此API的说明)。
    在循环等待中作如下处理:如果有消息发生,则通过PeekMessage取出此消息(但并不把它从消息循环中移除),然后调用 MsgWaitForMultipleObjects来等待线程Handle或SyncEvent出现Signaled状态,同时监听消息 (QS_SENDMESSAGE参数,详见MSDN中关于此API的说明)。可以把此API当作一个可以同时等待多个Handle的 WaitForSingleObject。如果是SyncEvent被SetEvent(返回WAIT_OBJECT_0 + 1),则调用CheckSynchronize处理同步方法。
    为什么在主线程中调用WaitFor必须用MsgWaitForMultipleObjects,而不能用WaitForSingleObject等待线 程结束呢?因为防止死锁。由于在线程函数Execute中可能调用Synchronize处理同步方法,而同步方法是在主线程中执行的,如果用 WaitForSingleObject等待的话,则主线程在这里被挂起,同步方法无法执行,导致线程也被挂起,于是发生死锁。
    而改用WaitForMultipleObjects则没有这个问题。首先,它的第三个参数为False,表示只要线程Handle或SyncEvent 中只要有一个Signaled即可使主线程被唤醒,至于加上QS_SENDMESSAGE是因为Synchronize是通过消息传到主线程来的,所以还 要防止消息被阻塞。这样,当线程中调用Synchronize时,主线程就会被唤醒并处理同步调用,在调用完成后继续进入挂起等待状态,直到线程结束。
    至此,对线程类TThread的分析可以告一个段落了,对前面的分析作一个总结:
    1、线程类的线程必须按正常的方式结束,即Execute执行结束,所以在其中的代码中必须在适当的地方加入足够多的对Terminated标志的判断,并及时退出。如果必须要“立即”退出,则不能使用线程类,而要改用API或RTL函数。
    2、对可视VCL的访问要放在Synchronize中,通过消息传递到主线程中,由主线程处理。
    3、线程共享数据的访问应该用临界区进行保护(当然用Synchronize也行)。
    4、线程通信可以采用Event进行(当然也可以用Suspend/Resume)。
    5、当在多线程应用中使用多种线程同步方式时,一定要小心防止出现死锁。
    6、等待线程结束要用WaitFor方法。

----------------------------------------------------------------------MY GOD--------------------------------------------------------------------

在Delphi中,多线程的应用是比较多的内容,同时由于现在CPU中多核的发展,开发多线程程序也显得较为重要,对于多线程包含有几种状态(创建,运行,挂起,唤醒,销毁)如不清晰的请自己查找相关的资料查看.本文本是在多线程中重点需要注意的几个方面的问题进行说明

  1.使用Synchronize方法
  对VCL的访问只能在主线程中进行,这意味着所有需要与用户打交道的代码都只能在主线程的环境中执行.通过TThread类中提供的Synchronize方法,可以实现此要求.对于此类的说明较多,不重点说明
  2.多线程的同步机制
  对于多线程的同步机制是说明的重点,主要是说明对于多线程几种同步的方式进行比较说明.在多线程中同步机制主要是采用Windows API函数提供的同步机制:临界区(Critical Section),互斥对象(Mutex)技术和信号量(Semaphore)技术.
  (1)临界区(只能用来同步单个进程中的线程)
   临界区技术是指源代码不能有两个线程同时执行的部分,实际上临界区是一小段代码,一次只允许一个线程执行这段代码.在这段代码中可以执行以前要求对某些共 享数据进行排它的存取.使用起来最为简单,但是它只能用来同步单个进程中的线程.临界区一次只允许一个线程取得对一个数据区的存取权.
  在使用临界区之前,必须使用InitializeCriticalSection过程进行初始化.其声明如下:
  procedure InitializeCriticalSection (var lpCriticalSection:TRTLCriticalSection);stdcall;
  使用临界区时可以使用EnterCriticalSection与LeaveCriticalSection来进行临界资源的管理
(2)互斥对象(可以被用来同步多个进程间的数据访问,如多程序访问打印机)
  它们可以被用来同步多个进程间的数据访问.互斥的一般使用流程是,进程必须首先用CreateMutex函数创建互斥对象,然后利用 WaitForSingleObject进入互斥环境,当用到同步的代码执行完成后,用ReleaseMutex解除互斥关系,当所有线程访问完后,调用 CloseHandle方法释放互斥对象.
  两个进程之间必须都用同一互斥对象的句柄.互斥对象实际上串行访问资源的全局对象.互斥对象不仅能在同一个程序下工作,而且可以在多个进程中同时工作.换句话说,它们既能够在单个应用程序下使两个或更多线程同步,也可以在不同的应用程序下进行多线程操作.
 
   注:临界区与互斥对象技术的区别如下:
  a.临界区调用的是EnterCriticalSection和LeaveCriticalSection,而互斥对象调用的是WaitForSingleObject和ReleaseMutex
  b.互斥对象可以用于跨进程边界同步线程
  c.我们可以给互斥对象取字符串类型的名字,对于一个已知的互斥对象,我们可以通过引用互斥对象的名字来使用它
 
 (3)信号量
  操作系统并不对某个线程拥有信号量进行跟踪.因此,一个线程等待某个信号量对象,观察由另一个线程来释放该信号量是可能的;另一个区别是,信号量有一个与 之联系的资源计数,一个互斥对象只允许一个线程获得对它的访问许可,而一个信号量却允许多个线程同时获得对它的访问许可.
  信号量创建时使用CreateSeaphore来创建,调用WaitForSingleObject来计数值加1,用ReleaseSemphore来释放一个信号量,信号量的关闭采用CloseHandle进行处理
----------------------------------------------------------------OHH MY GOD-------------------------------------------------------------------
一、临界区

所谓临界区,就是一次只能由一个线程来执行的一段代码。如果把初始化数组的代码放在临界区内,另一个线程在第一个线程处理完之前是不会被执行的。

使用临界区的步骤:

1、先声明一个全局变量类型为TRTLCriticalSection;

2、在线程Create()前调用InitializeCriticalSection()过程来初始化,该函数定义是:

void WINAPI InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

类型lpCriticalSection即是Delphi封装的TRTLCriticalSection。

3、在线程的需要放入临界区的代码前面使用EnterCriticalSection(lpCriticalSection)过程来开始建立临界区。在代码完成后用LeaveCriticalSection(lpCriticalSection)来标志临界区的结束。

4、在线程执行完后用DeleteCriticalSection(lpCriticalSection)来清除临界区。这个清除过程必须放在线程 执行完后的地方,比如FormDesroy事件中。上面的例子中,若把该过程放在TMyThread.Create(False);后,会产生错误。

二、互斥:

互斥非常类似于临界区,除了两个关键的区别:首先,互斥可用于跨进程的线程同步。其次,互斥能被赋予一个字符串名字,并且通过引用此名字创建现有互斥对象的附加句柄。

提示临界区与事件对象(比如互斥对象)的最大的区别是在性能上。临界区在没有线程冲突时,要用10~15个时间片,而事件对象由于涉及到系统内核要用400~600个时间片。

使用互斥的步骤:

1、声明一个类型为Thandle或Hwnd的全局变量,其实都是Cardinal类型。Hwnd是handle of window,主要用于窗口句柄;而Thandle则没有限制。

2、线程Create()前用CreateMutex()来创建一个互斥量。该函数定义为:

HANDLE WINAPI CreateMutex(

LPSECURITY_ATTRIBUTES lpMutexAttributes,

BOOL bInitialOwner,

LPCTSTR lpName:Pchar);

LPSECURITY_ATTRIBUTES参数为一个指向TSecurityAttributtes记录的指针。此参数设为nil,表示访问控制列表默认的安全属性。

bInitalOwner参数表示创建互斥对象的线程是否要成为此互斥对象的拥有者。当此参数为False时,表示互斥对象没有拥有者。

lpName参数指定互斥对象的名称。设为nil表示无命名,如果参数不是设为nil,函数会搜索是否有同名的互斥对象存在。如果有,函数就会返回同名互斥对象的句柄。否则,就新创建一个互斥对象并返回其句柄。

返回值是一handle。当错误发生时,返回null,此时用GetLastError函数可查看错误的信息。

利用CreateMutex()可以防止程序多个实例运行,如下例:

Program ABC;

Uses Forms,Windows,…;

{$R *.res}

Var

hMutex:Hwnd;

Begin

Application.Initialize;

hMutex:=CreateMutex(nil,False,Pchar(Application.Title));

if GetLastError<>ERROR_ALREADY_EXISTS then

begin

//项目要运行的咚咚

end;

ReleaseMutex(hMutex);

Application.Run;

End;

在本节的例程中,我们只是要防止线程进入同步代码区域中,所以lpName参数设置为nil。

3、在同步代码前用WaitForSingleObject()函数。该函数使得线程取得互斥对象(同步代码)的拥有权。该函数定义为:

DWORD WINAPI WaitForSingleObject(

HANDLE hHandle,

DWORD dwMilliseconds);

这个函数可以使当前线程在dwMilliseconds指定的时间内睡眠,直到hHandle参数指定的对象进入发信号状态为止。一个互斥对象不再 被线程拥有时,它就进入发信号状态。当一个进程要终止时,它就进入发信号状态。dwMilliseconds参数可以设为0,这意味着只检查 hHandle参数指定的对象是否处于发信号状态,而后立即返回。dwMilliseconds参数设为INFINITE,表示如果信号不出现将一直等下 去。

这个函数的返回值含义:

WAIT_ABANDONED 指定的对象是互斥对象,并且拥有这个互斥对象的线程在没有释放此对象之前就已终止。此时就称互斥对象被抛弃。这种情况下,这个互斥对象归当前线程所有,并把它设为非发信号状态

WAIT_OBJECT_0 指定的对象处于发信号状态

WAIT_TIMEOUT 等待的时间已过,对象仍然是非发信号状态

再次声明,当一个互斥对象不再被一个线程所拥有,它就处于发信号状态。此时首先调用WaitForSingleObject()函数的线程就成为该 互斥对象的拥有者,此互斥对象设为不发信号状态。当线程调用ReleaseMutex()函数并传递一个互斥对象的句柄作为参数时,这种拥有关系就被解 除,互斥对象重新进入发信号状态。

注意除WaitForSingleObject()函数外,你还可以使用WaitForMultipleObject()和 MsgWaitForMultipleObject()函数,它们可以等待几个对象变为发信号状态。这两个函数的详细情况请看Win32 API联机文档。

4、在同步代码结束后,使用ReleaseMutex(THandle)函数来标志。该函数只是用来解除线程与互斥对象的拥有关系,并不释放互斥对象的句柄。

5、调用CloseHandle(THandle)来关闭互斥对象。请注意例程中该函数的使用位置。

三、还有一种用信号量对象来管理线程同步的,它是在互斥的基础上建立的,但信号量增加了资源计数的功能,预定数目的线程允许同时进入要同步的代码。有点复杂,想不到在哪可以用,现在就不研究论了。

unit Tst_Thread3U;

interface

uses

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

type
TForm1 = class(TForm)
Button1: TButton;
Memo1: TMemo;
Button2: TButton;
Button3: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
private
procedure ThreadsDone(Sender: TObject);
end;

TMyThread=class(TThread)
protected
procedure Execute;override;
end;

var
Form1: TForm1;

implementation
{$R *.dfm}

const
MaxSize=128;
var
NextNumber:Integer=0;
DoneFlags:Integer=0;
GlobalArry:array[1..MaxSize] of Integer;
Lock:byte; //1-不同步 2-临界区 3-互斥
CS:TRTLCriticalSection; //临界区
hMutex:THandle; //互斥

function GetNextNumber:Integer;
begin
Result:=NextNumber;
inc(NextNumber);
end;

procedure TMyThread.Execute;
var
i:Integer;
begin
FreeOnTerminate:=True; //终止后自动free
OnTerminate:=Form1.ThreadsDone;
if Lock<>3 then //非互斥情况
begin
if Lock=2 then EnterCriticalSection(CS); //建立临界区
for i := 1 to MaxSize do
begin
GlobalArry[i]:=GetNextNumber;
Sleep(5);
end;
if Lock=2 then LeaveCriticalSection(CS);//离开临界区
end else //-------互斥
begin
if WaitForSingleObject(hMutex,INFINITE)=WAIT_OBJECT_0 then
begin
for i := 1 to MaxSize do
begin
GlobalArry[i]:=GetNextNumber;
Sleep(5);
end;
end;
ReleaseMutex(hMutex); //释放
end;
end;

procedure TForm1.ThreadsDone(Sender: TObject);
var
i:Integer;
begin
Inc(DoneFlags);
if DoneFlags=2 then
begin
for i := 1 to MaxSize do
Memo1.Lines.Add(inttostr(GlobalArry[i]));
if Lock=2 then DeleteCriticalSection(CS); //删除临界区
If Lock=3 then CloseHandle(hMutex); //关闭互斥
end;
end;

//非同步
procedure TForm1.Button1Click(Sender: TObject);
begin
Lock:=1;
TMyThread.Create(False);
TMyThread.Create(False);
end;

//临界区
procedure TForm1.Button2Click(Sender: TObject);
begin
Lock:=2;
InitializeCriticalSection(CS); //初始化临界区
TMyThread.Create(False);
TMyThread.Create(False);
end;

//互斥
procedure TForm1.Button3Click(Sender: TObject);
begin
Lock:=3; // 互斥
hMutex:=CreateMutex(0,False,nil);
TMyThread.Create(False);
TMyThread.Create(False);
end;

end.
----------------------------------------------------------------my god-----------------------------------------------------------------
       多核时代的到来,对于我们程序员来说要尽快的,尽可能多的使用多线程编程只有这样,作的程序才会有高效率,这个思想一定要宣传啊,不然多核时代了,还写单线程的程序就太不够档次了。


delphi中多线程同步的一些方法
当有多个线程的时候,经常需要去同步这些线程以访问同一个数据或资源。例如,假设有一个程序, 其中一个线程用于把文件读到内存,而另一个线程用于统计文件中的字符数。当然,在把整个文件调入内存之前,统计它的计数是没有意义的。但是,由于每个操作 都有自己的线程,操作系统会把两个线程当作是互不相干的任务分别执行,这样就可能在没有把整个文件装入内存时统计字数。为解决此问题,你必须使两个线程同 步工作。
存在一些线程同步地址的问题,Win32提供了许多线程同步的方式。在本节你将看到使用临界区、 互斥、信号量和事件来解决线程同步的问题。

1. 临界区
临界区是一种最直接的线程同步方式。所谓临界区,就是一次只能由一个线程来执行的一段代码。如果把初始化数组的代码放在临界区内,另一个线程在第一个线程处理完之前是不会被执行的。
在使用临界区之前,必须使用InitializeCriticalSection()过程来初始化它。
其声明如下:
     procedure InitializeCriticalSection(var
lpCriticalSection 参数是一个TRTLCriticalSection类型的记录,并且是变参。至于TRTLCriticalSection 是如何定义的,这并不重要,因为很少需要查看这个记录中的具体内容。只需要在lpCriticalSection中传递未初始化的记 录,InitializeCriticalSection()过程就会填充这个记录。
注意Microsoft故意隐瞒了 TRTLCriticalSection的细节。因为,其内容在不同的硬件平台上是不同的。在基于Intel的平台 上,TRTLCriticalSection包含一个计数器、一个指示当前线程句柄的域和一个系统事件的句柄。在Alpha平台上,计数器被替换为一种 Alpha-CPU 数据结构,称为spinlock。在记录被填充后,我们就可以开始创建临界区了。这时我们需要用EnterCriticalSection()和 LeaveCriticalSection()来封装代码块。这两个过程的声明如下:
   
procedure EnterCriticalSection(var lpCriticalSection:TRRLCriticalSection);stdcall;
procedure LeaveCriticalSection(var
正如你所想的,参数lpCriticalSection就是由InitializeCriticalSection()填充的记录。
当你不需要TRTLCriticalSection记录时,应当调用DeleteCriticalSection()过程,下面是它的声明:
procedure DeleteCriticalSection(var


2. 互斥
互斥非常类似于临界区,除了两个关键的区别:首先,互斥可用于跨进程的线程同步。其次,互斥能被赋予一个字符串名字,并且通过引用此名字创建现有互斥对象的附加句柄。
提示临界区与事件对象(比如互斥对象)的最大的区别是在性能上。临界区在没有线程冲突时,要用1 0 ~ 1 5个时间片,而事件对象由于涉及到系统内核要用400~600个时间片。
可以调用函数CreateMutex ( )来创建一个互斥量。下面是函数的声明:
function

lpMutexAttributes参数为一个指向TSecurityAttributtes记录的指针。此参数通常设为0,表示默认的安全属性。 bInitalOwner参数表示创建互斥对象的线程是否要成为此互斥对象的拥有者。当此参数为False时, 表示互斥对象没有拥有者。
lpName参数指定互斥对象的名称。设为nil表示无命名,如果参数不是设为nil,函数会搜索是否有同名的互斥对象存在。如果有,函数就会返回同名互斥对象的句柄。否则,就新创建一个互斥对象并返回其句柄。
当使用完互斥对象时,应当调用CloseHandle()来关闭它。

在程序中使用WaitForSingleObject()来防止其他线程进入同步区域的代码。此函数声明如下:
function


这个函数可以使当前线程在dwMilliseconds指定的时间内睡眠,直到hHandle参数指定的对象进入发信号状态为止。一个互斥 对象不再被线程拥有时,它就进入发信号状态。当一个进程要终止时,它就进入发信号状态。dwMilliseconds参数可以设为0,这意味着只检查 hHandle参数指定的对象是否处于发信号状态,而后立即返回。dwMilliseconds参数设为INFINITE,表示如果信号不出现将一直等下 去。
这个函数的返回值如下
WaitFor SingleObject()函数使用的返回值
返回值 含义
WAIT_ABANDONED 指定的对象是互斥对象,并且拥有这个互斥对象的线程在没有释放此对象之前就已终止。此时就称互斥对象被抛弃。这种情况下,这个互斥对象归当前线程所有,并把它设为非发信号状态
WAIT_OBJECT_0 指定的对象处于发信号状态
WAIT_TIMEOUT 等待的时间已过,对象仍然是非发信号状态再次声明,当一个互斥对象不再被一个线程所拥有,它就处于发信号状态。此时首先调用 WaitForSingleObject()函数的线程就成为该互斥对象的拥有者,此互斥对象设为不发信号状态。当线程调用ReleaseMutex() 函数并传递一个互斥对象的句柄作为参数时,这种拥有关系就被解除,互斥对象重新进入发信号状态。
注意除 WaitForSingleObject()函数外,你还可以使用WaitForMultipleObject()和 MsgWaitForMultipleObject()函数,它们可以等待几个对象变为发信号状态。这两个函数的详细情况请看Win32 API联机文档。

3. 信号量
另一种使线程同步的技术是使用信号量对象。它是在互斥的基础上建立的,但信号量增加了资源计数的功能,预定数目的线程允许同时进入要同步的代码。可以用CreateSemaphore()来创建一个信号量对象,其声明如下:
function

和CreateMutex()函数一样,CreateSemaphore()的第一个参数也是一个指向TSecurityAttribute s记录的指针,此参数的缺省值可以设为nil。
lInitialCount 参数用来指定一个信号量的初始计数值,这个值必须在0和lMaximumCount之间。此参数大于0,就表示信号量处于发信号状态。当调用 WaitForSingleObject()函数(或其他函数)时,此计数值就减1。当调用ReleaseSemaphore()时,此计数值加1。
参数lMaximumCount指定计数值的最大值。如果这个信号量代表某种资源,那么这个值代表可用资源总数。
参数lpName用于给出信号量对象的名称,它类似于CreateMutex()函数的lpName参数。

——————————————————————————————————————————

★★★关于线程同步:
Synchronize()是在一个隐蔽的窗口里运行,如果在这里你的任务很繁忙,你的主窗口会阻塞掉;Synchronize()只是将该线程的代码放到主线程中运行,并非线程同步。

临界区是一个进程里的所有线程同步的最好办法,他不是系统级的,只是进程级的,也就是说他可能利用进程内的一些标志来保证该进程内的线程同步,据 Richter说是一个记数循环;临界区只能在同一进程内使用;临界区只能无限期等待,不过2k增加了TryEnterCriticalSection函 数实现0时间等待。

互斥则是保证多进程间的线程同步,他是利用系统内核对象来保证同步的。由于系统内核对象可以是有名字的,因此多个进程间可以利用这个有名字的内核对 象保证系统资源的线程安全性。互斥量是Win32 内核对象,由操作系统负责管理;互斥量可以使用WaitForSingleObject实现无限等待,0时间等待和任意时间等待。

1. 临界区
临界区是一种最直接的线程同步方式。所谓临界区,就是一次只能由一个线程来执行的一段代码。如果把初始化数组的代码放在临界区 内,另一个线程在第一个线程处理完之前是不会被执行的。在使用临界区之前,必须使用InitializeCriticalSection()过程来初始化 它。
在第一个线程调用了EnterCriticalSection()之后,所有别的线程就不能再进入代码块。下一个线程要等第一个线程调用LeaveCriticalSection()后才能被唤醒。

2. 互斥
互斥非常类似于临界区,除了两个关键的区别:首先,互斥可用于跨进程的线程同步。其次,互斥能被赋予一个字符串名字,并且通过引用此名字创建现有互斥对象的附加句柄。
提示:临界区与事件对象(比如互斥对象)的最大的区别是在性能上。临界区在没有线程冲突时,要用10 ~ 15个时间片,而事件对象由于涉及到系统内核要用400~600个时间片。
当 一个互斥对象不再被一个线程所拥有,它就处于发信号状态。此时首先调用WaitForSingleObject()函数的线程就成为该互斥对象的拥有者, 此互斥对象设为不发信号状态。当线程调用ReleaseMutex()函数并传递一个互斥对象的句柄作为参数时,这种拥有关系就被解除,互斥对象重新进入 发信号状态。
可以调用函数CreateMutex()来创建一个互斥量。当使用完互斥对象时,应当调用CloseHandle()来关闭它。

3. 信号量
另一种使线程同步的技术是使用信号量对象。它是在互斥的基础上建立的,但信号量增加了资源计数的功能,预定数目的线程允许同时进入要同步的代码。可以用CreateSemaphore()来创建一个信号量对象,
因为只允许一个线程进入要同步的代码,所以信号量的最大计数值(lMaximumCount)要设为1。ReleaseSemaphore()函数将使信号量对象的计数加1;
记住,最后一定要调用CloseHandle()函数来释放由CreateSemaphore()创建的信号量对象的句柄。

★★★WaitForSingleObject函数的返值:
WAIT_ABANDONED指定的对象是互斥对象,并且拥有这个互斥对象的线程在没有释放此对象之前就已终止。此时就称互斥对象被抛弃。这种情况下,这个互斥对象归当前线程所有,并把它设为非发信号状态;
WAIT_OBJECT_0 指定的对象处于发信号状态;
WAIT_TIMEOUT等待的时间已过,对象仍然是非发信号状态;

——————————————————————————————————————————————
VCL支持三种技术来达到这个目的:
(2) 使用critical区
如 果对象没有提高内置的锁定功能,需要使用critical区,Critical区在同一个时间只也许一个线程进入。为了使用Critical区,产生一个 TCriticalSection全局的实例。TcriticalSection有两个方法,Acquire(阻止其他线程执行该区域)和 Release(取消阻止)

  每个Critical区是与你想要保护的全局内存相关联。每个访问全局内存的线程必须首先使用Acquire来保证没有其他线程使用它。完成以后,线程调用Release方法,让其他线程也可以通过调用Acquire来使用这块全局内存。

  警告:Critical区只有在所有的线程都使用它来访问全局内存,如果有线程直接调用内存,而不通过Acquire,会造成同时访问的问题。 例如:LockXY是一个全局的Critical区变量。任何一个访问全局X, Y的变量的线程,在访问前,都必须使用Acquire
LockXY.Acquire; { lock out other threads }
try
Y := sin(X);
finally
LockXY.Release;
end
临界区主要是为实现线程之间同步的,但是使用的时候注意,一定要在用此临界对象同步的线程之外建立该对象(一般在主线程中建立临界对象)。

————————————————————————————————————————————————
线程同步使用临界区,进程同步使用互斥对象。

Delphi中封装了临界对象。对象名为TCriticalSection,使用的时候只要在主线程当中建立这个临界对象(注意一定要在需要同步的线程之外建立这个对象)。具体同步的时候使用Lock和Unlock即可。
而 进程间同步建立互斥对象,则只需要建立一个互斥对象CreateMutex. 需要同步的时候只需要WaitForSingleObject(mutexhandle, INFINITE) unlock的时候只需要ReleaseMutex(mutexhandle);即可。

有很多方法, 信号灯, 临界区, 互斥对象,此外, windows下还可以用全局原子,共享内存等等. 在windows体系中, 读写一个8位整数时原子的, 你可以依靠这一点完成互斥的方法. 对于能够产生全局名称的方法能够可以在进程间同步上(如互斥对象), 也可以用在线程间同步上;不能够产生全局名称的方法(如临界区)只能用在线程间同步上.


转自:http://blog.csdn.net/houyichong/archive/2008/01/10/2034634.aspx