◆Delphi多线程编程之三 同步读写全局数据 ◆(乌龙哈里2008-10-12)

Delphi多线程编程之三同步读写全局数据 ◆(乌龙哈里2008-10-12

(调试环境:Delphi2007+WinXPsp3 例程:Tst_Thread3.dpr

     开始研究最重要的多线程读写全局数据了,结合书上的例子,我修改成下面的情况:

 

 

  1. unit Tst_Thread3U;
  2. interface
  3. uses
  4.   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,Dialogs, StdCtrls;
  5. type
  6.   TForm1 = class(TForm)
  7.     Button1: TButton;
  8.     Memo1: TMemo;
  9.     Button2: TButton;
  10.     Button3: TButton;
  11.     procedure Button1Click(Sender: TObject);
  12.     procedure Button2Click(Sender: TObject);
  13.     procedure Button3Click(Sender: TObject);
  14.   private
  15.     procedure ThreadsDone(Sender: TObject);
  16.   end;
  17.   TMyThread=class(TThread)
  18.   protected
  19.       procedure Execute;override;
  20.   end;
  21. var
  22.   Form1: TForm1;
  23. implementation
  24. {$R *.dfm}
  25. const
  26.   MaxSize=128;
  27. var
  28.   NextNumber:Integer=0;
  29.   DoneFlags:Integer=0;
  30.   GlobalArry:array[1..MaxSize] of Integer;
  31.   Lock:byte;   //1-不同步  2-临界区 3-互斥
  32.   CS:TRTLCriticalSection; //临界区
  33.   hMutex:THandle;  //互斥
  34. function GetNextNumber:Integer;
  35. begin
  36.   Result:=NextNumber;
  37.   inc(NextNumber);
  38. end;
  39. procedure TMyThread.Execute;
  40. var
  41.   i:Integer;
  42. begin
  43.   FreeOnTerminate:=True; //终止后自动free
  44.   OnTerminate:=Form1.ThreadsDone;
  45.   if Lock<>3 then     //非互斥情况
  46.   begin
  47.     if Lock=2 then EnterCriticalSection(CS); //建立临界区
  48.     for i := 1 to MaxSize do
  49.     begin
  50.       GlobalArry[i]:=GetNextNumber;
  51.       Sleep(5);
  52.     end;
  53.     if Lock=2 then LeaveCriticalSection(CS);//离开临界区
  54.   end else      //-------互斥
  55.   begin
  56.     if WaitForSingleObject(hMutex,INFINITE)=WAIT_OBJECT_0 then
  57.     begin
  58.       for i := 1 to MaxSize do
  59.       begin
  60.         GlobalArry[i]:=GetNextNumber;
  61.         Sleep(5);
  62.       end;
  63.     end;
  64.     ReleaseMutex(hMutex);   //释放
  65.   end;
  66. end;
  67. procedure TForm1.ThreadsDone(Sender: TObject);
  68. var
  69.   i:Integer;
  70. begin
  71.   Inc(DoneFlags);
  72.   if DoneFlags=2 then
  73.   begin
  74.     for i := 1 to MaxSize do
  75.       Memo1.Lines.Add(inttostr(GlobalArry[i]));
  76.       if Lock=2 then DeleteCriticalSection(CS); //删除临界区
  77.        If Lock=3 then CloseHandle(hMutex); //关闭互斥
  78.   end;
  79. end;
  80. //非同步
  81. procedure TForm1.Button1Click(Sender: TObject);
  82. begin
  83.   Lock:=1;
  84.   TMyThread.Create(False);
  85.   TMyThread.Create(False);
  86. end;
  87. //临界区
  88. procedure TForm1.Button2Click(Sender: TObject);
  89. begin
  90.   Lock:=2;  
  91.   InitializeCriticalSection(CS); //初始化临界区
  92.   TMyThread.Create(False);
  93.   TMyThread.Create(False);
  94. end;
  95. //互斥
  96. procedure TForm1.Button3Click(Sender: TObject);
  97. begin
  98.   Lock:=3// 互斥
  99.   hMutex:=CreateMutex(0,False,nil);
  100.   TMyThread.Create(False);
  101.   TMyThread.Create(False);
  102. end;
  103. end.

没有临界区和互斥的帮助,两个线程都不断地在Memo1输出,而且数字是乱的。

 

 

 

 

 

 

 

一、临界区

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

使用临界区的步骤:

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、声明一个类型为ThandleHwnd的全局变量,其实都是Cardinal类型。Hwndhandle 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)来关闭互斥对象。请注意例程中该函数的使用位置。

 

 

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值