WIN32下DELPHI中的多线程【同步1】(四)

线程的同步
     在使用的时候,多线程最让人头疼的也许就是同步了。
     如果你的线程只是完成一件并不需要访问线程对象外部资源的工作,在这种情况下,线程互相之间不需要进行通信,此时Windows的运行性能最好。但是,线程很少能够在所有的时间都独立地进行操作。通常情况下,要生成一些线程来处理某个任务。当这个任务完成时,另一个线程必须了解这个情况。
    系统中的所有线程都必须拥有对各种系统资源的访问权,这些资源包括内存堆栈,文件,窗口和许多其他资源。如果一个线程需要独占对资源的访问权,那么其他线程就无法完成它们的工作。反过来说,也不能让任何一个线程在任何时间都能访问所有的资源。如果在一个线程从内存块中读取数据时,另一个线程却想要将数据写入同一个内存块,那么这就像你在读一本书时另一个人却在修改书中的内容一样。这样,书中的内容就会被搞得乱七八糟,结果什么也看不清楚。
     线程需要在下面两种情况下互相进行通信
    • 当有多个线程访问共享资源而不使资源被破坏时。
    • 当一个线程需要将某个任务已经完成的情况通知另外一个或多个线程时。
     线程的同步包括许多方面的内容,Windows提供了许多方法,可以非常容易地实现线程的同步。但是,要想随时了解一连串的线程想要做什么,那是非常困难的。我们的头脑的工作不是异步的,我们希望以一种有序的方式来思考许多事情,每次前进一步。不过多线程环境不是这样运行的。你几乎无法完全知道目标系统中存在多少线程,也不知道他们处在什么状态下,更不知道他们要干什么。

用户方式下的线程同步

1、互锁函数
  在MSDN关于同步函数的帮助文档中,你会看到大量的互锁函数。他们大多以Interlocked****的名字存在。互锁函数运行在用户模式。它能保证当一个线程访问一个变量时,其它线程无法访问此变量,以确保变量值的唯一性。这种访问方式被称为原子访问。

常用的互锁函数及其功能见如下列表:

函数 参数和功能
InterlockedIncrement   参数为PLONG类型。此函数使一个LONG变量增1
InterlockedDecrement             参数为PLONG类型。此函数使一个LONG变量减1
InterlockedExchangeAdd           参数1为PLONG类型,参数2为LONG类型。此函数将参数2赋给参数1指向的值
InterlockedExchange              参数1为PLONG类型,参数2为LONG类型。此函数将参数2的值赋给参数1指向的值
InterlockedExchangePointer       参数为PVOID* 类型,参数2为PVOID类型。此函数功能同上。

 
       用 InterlockedExchangeAdd来说明,他接受一个长整形变量的地址,然后将参数2的树枝加到参数1制定的长整形数据上。我们前边已经说了,他能保证当一个线程访问此长整形变量时,其他线程无法访问此变量,那么他是如何实现的呢?答案取决于运行的是何种CPU平台。对于x86家族的CPU来说,互锁函数会对总线发出一个硬件信号,防止另一个CPU访问同一个内存地址。在Alpha平台上,互锁函数能够执行下列操作
1) 打开C P U中的一个特殊的位标志,并注明被访问的内存地址。
2) 将内存的值读入一个寄存器。
3) 修改该寄存器。
4) 如果C P U中的特殊位标志是关闭的,则转入第二步。否则,特殊位标志仍然是打开的,寄存器的值重新存入内存。
        互锁函数工作与用户模式之下,所以他的速度是非常快点的。有利就有弊,互锁函数最大的缺点莫过于使用范围的狭隘性了,它更多的只是对单个变量的保护。

2、临界区
     也有的地方叫它关键代码段临界区指一个小代码段,在代码能够执行前,它必须独占对某些共享资源的访问权。这是让若干行代码能够“以原子操作方式”来使用资源的一种方法。所谓原子操作方式,是指该代码知道没有别的线程要访问该资源。简单的说,就是一次只能由一个线程来执行的一段代码。
      先来看一段例子,在例子中,线程完成对一个数组初始化的工作,目标是在当前的数值上加1,TThread对象中还定义了一个FUseCritical的变量,它用来决定线程在执行时是否要使用临界区的方式。

 

{
   作者:wudi_1982
   联系方式:wudi_1982@hotmail.com
   本代码为演示代码,只贴出了一些比较重要的代码
    转载请著名出处
}


const
  MaxArray 
=   1000 ; // 公共内存区域的大小

// 演示临界区功能的线程类
type
  TCriticalSectionThread
=   class (TThread)
    
private
      FUseCritical : Boolean;
// 决定是否使用临界区
      procedure GetRestult;
    
protected
      procedure Execute;
override ;
    
public
       constructor Create(UseCritical : Boolean);
  end;
end;

....

var
  PublicMem : array[
0 ..MaxArray] of integer; // 一块公共区域
  Cs : TRTLCriticalSection; // 描述临界区信息的数据结构

实现代码如下:
{ TCriticalSectionThread }

constructor TCriticalSectionThread.Create(UseCritical: Boolean);
begin
   
// 构造函数中接受是否使用临界区的参数
   FUseCritical : =  UseCritical;
   inherited Create(
false );
end;

procedure TCriticalSectionThread.Execute;
var
  i : integer;
begin
  inherited;
  
// 运行完毕后自动释放资源
  FreeOnTerminate : =  True;
  
// 如果使用了临界区,则进入临界区
   if  FUseCritical then
  EnterCriticalSection(cs);
  
// 对数组初始化
   for  i : =   0  to MaxArray  do
    inc(PublicMem[i]);
  Synchronize(GetRestult);
  
// 离开
   if  FUseCritical then
  LeaveCriticalSection(Cs);
end;

procedure TCriticalSectionThread.GetRestult;
var
  i : integer;
begin
  
// 将结果显示在Form1.listbox1中
   for  i : =   0  to MaxArray  do
    Form1.ListBox1.Items.Add(inttostr(PublicMem[i]));
end;

// 调用这个线程类来演示临界区功能的代码
procedure TForm1.Button3Click(Sender: TObject);
begin
  
// 将公共内存区域填充为0
  FillMemory(@PublicMem, sizeof (PublicMem), 0 );
  ListBox1.Clear;
  
// 一个CheckBox,用来接受是否使用临界区方式的信息
   if  ckbxUsesC.Checked then
  
// 如果使用临界区,则首先初始化
    InitializeCriticalSection(cs);
  
// 同时生成两个线程
  TCriticalSectionThread.Create(ckbxUsesC.Checked);
  TCriticalSectionThread.Create(ckbxUsesC.Checked);
end;

// 好的编码习惯中,你应该在确定线程已经不再需要使用临界区时,用DeleteCriticalSection(cs);清楚这个结构

       整理上面代码,并执行,在MaxArray定义比较大的时候(也就是一个线程完成工作需要时间比较长的时候),你会发现当不使用临界区时,两个进程“同时”出现对公共内存区的访问,数组没有按照你的预定方式进行初始化(理想的情况是数组先被初始化为1,然后再是2,可实际情况,你可能看到在第一个线程对数组初始化时,第二个线程抢占了CPU,所以,大量的数组成员被改写成了2),当使用临界区时,你会发现数组按照我们设定的思路先被初始化为1,然后再初始化为2。
       让我们来看看在使用临界区的情况下系统是如何调度这两个线程的,当第一个线程创建之后,它成为可调度状态,至于系统目前是否分配CPU时间片给它,我们不知道,这要看系统中其他进程的情况,然后第二个线程创建,并且也成为可调度状态,当系统发现可以调度这两个线程的时候(也许在第一个线程创建之后,它就已经被调度了),系统调度一个线程,我们这里暂且假设调度的是第一个线程,线程执行EnterCriticalSection(cs)更新CRITICAL_SECTION(DLEPHI中将它定义为TRTLCriticalSection的记录)的成员变量,以指明调用线程已被赋予访问权并立即返回,使该线程能够继续运行,然后在一定时间之后,线程2抢占了CPU,然后执行EnterCriticalSection(cs),刷新CRITICAL_SECTION的成员变脸,系统发现一个线程已经被赋予了资源的访问权,这时,系统将调用线程(我们这里的线程2)置于等待状态。这种情况是极好的,因为等待的线程不会浪费任何CPU 时间。系统能够记住该线程想要访问该资源并且自动更新CRITICAL_SECTION的成员变量,直到线程1重新被调度,并执行了LeaveCriticalSection函数,这时系统便将线程2置为可调度状态,然后在合适的时间,线程2被调度,再次完成对数组初始化的工作。

使用临界区时要注意的内容
1、EnterCriticalSection和LeaveCriticalSection要配对出现,如果你只调用EnterCriticalSection而忘记使用LeaveCriticalSection,那么结果将是可怕的,这意味着其他需要访问被保护资源的线程将永远的等待下去,直到最终超时,产生一个异常条件。超时的时间被定义在注册表的HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Session Manager中,默认大约时30天的时间。
2、CRITICAL_SECTION结构中的成员应该在任何线程试图访问被保护的资源之前初始化。你要调用InitializeCriticalSection来完成此操作。如果一个线程试图进入未初始化的CRITICAL_SECTION,那么结果将是无法预料的。
3、当知道进程的线程不再试图访问共享资源时,用DeleteCriticalSection函数清楚CRITICAL_SECTION结构

几个有用的技巧
1)、每个需保护的共享资源使用一个单独的CRITICAL_SECTION变量,例如线程1和线程2访问一个资源A,而线程1和线程3访问另一个资源B,那最好都资源A、B都使用一个单独的CRITICAL_SECTION变量,如果使用同一个CRITICAL_SECTION变量,那么加入线程1先被调度了,线程2、3则只有等待线程1完成对A、B的使用后才有机会运行。如果使用单独的CRITICAL_SECTION变量,则在线程1使用完A之后,线程2即可迅速成为被调度状态。
2)、当要访问多个被保护资源时,如果你使用的不同的CRITICAL_SECTION变量,那么要注意他们的顺序,例如线程1先后调用EnterCriticalSection(cs1);EnterCriticalSection(cs2),而有另外一个线程则使用另外的顺序EnterCriticalSection(cs2),EnterCriticalSection(cs1),那么将有可能出现死锁的情况,线程1被调度,获得被cs1保护的资源A的使用权,然后线程2被调度,获得了被cs2保护的资源B的使用权,那么此时,无论是线程1,还是线程2,都将因为对方而一直等待下去。
3)、不要在临界区内长时间执行那些不需要使用保护资源的代码。如果长时间执行那些不必要保护的代码,其他的希望访问保护资源的线程将长时间的等待下去。

线程与内核对象的同步
       用户方式同步的优点是它的同步速度非常快。虽然用户方式的线程同步机制具有速度快的优点,但其局限性也是明显的。例如,互锁函数家族只能在单值上运行,根本无法使线程进入等待状态。我们使用临界区的方式可以使得线程进入等待状态,但是只能用这些代码段对单个进程中的线程实施同步。因为在等待进临界区时你无法方便的设定超时值,所以有可能出现死锁的状态。
     上面我一直强调了一个概念,就是等待状态,这是很重要的,因为等待状态的线程将不使用CPU资源。
      内核对象机制的适应性远远优于用户方式机制。实际上,内核对象机制的唯一不足之处是它的速度比较慢。《WINDOWS核心编程》一书中说”这个转换需要很大的代价:往返一次需要占用x86平台上的大约1000个CPU周期,当然,这还不包括执行内核方式代码,即实现线程调用的函数的代码所需的时间。“,我没有测试过,不知道这1000个CPU周期的说法是否准确,但可以肯定内核对象机制将比用户模式下的同步要慢。
      Windows的内核对象,包括进程,线程和作业等。可以将所有这些内核对象用于同步目的。对于线程同步来说,这些内核对象中的每种对象都可以说是处于已通知或未通知的状态之中。例如,进程内核对象总是在未通知状态中创建的。当进程终止运行时,操作系统自动使该进程的内核对象处于已通知状态。一旦进程内核对象得到通知,它将永远保持这种状态,它的状态永远不会改为未通知状态。当进程正在运行的时候,进程内核对象处于未通知状态,当进程终止运行的时候,它就变为已通知状态。
      实际上,线程内核对象也遵循同样的规则。与进程内核对象一样,线程内核对象也可以处于已通知状态或未通知状态。
     下面的内核对象可以处于已通知状态或未通知状态:进程、文件修改通知线程、事件、作业、 可等待定时器、文件、 信标、控制台输入、 互斥对象
     线程可以使自己进入等待状态,直到一个对象变为已通知状态。注意,用于控制每个对象的已通知/未通知状态的规则要根据对象的类型而定。

等待函数
    最常用的莫过于WaitForSingleObject
    DWORD WaitForSingleObject(HANDLE hObject,DWORD dwMillseconds);
    当线程调用该函数时,第一个参数标识一个能够支持被通知/未通知的内核对象。第二个参数允许该线程指明为了等待该对象变为已通知状态,它将等待多长时间。WaitForSingleObject的返回值能够指明调用线程为什么再次变为可调度状态。如果线程等待的对象变为已通知状态,那么返回值是WAIT_OBJECT_0。如果设置的超时已经到期,则返回值是WAIT_TIMEOUT。如果将一个错误的值(如一个无效句柄)传递给WaitForSingleObject,那么返回值将是WAIT_FAILED。

事件对象
       在所有的内核对象中,事件内核对象是个最基本的对象。它们包含一个使用计数(与所有内核对象一样),一个用于指明该事件是个自动重置的事件还是一个人工重置的事件的布尔值,另一个用于指明该事件处于已通知状态还是未通知状态的布尔值。
       事件能够通知一个操作已经完成。有两种不同类型的事件对象。一种是人工重置的事件,另一种是自动重置的事件。当人工重置的事件得到通知时,等待该事件的所有线程均变为可调度线程。当一个自动重置的事件得到通知时,等待该事件的线程中只有一个线程变为可调度线程。
       当一个线程执行初始化操作,然后通知另一个线程执行剩余的操作时,事件使用得最多。事件初始化为未通知状态,然后,当该线程完成它的初始化操作后,它就将事件设置为已通知状态。这时,一直在等待该事件的另一个线程发现该事件已经得到通知,因此它就变成可调度线程。这第二个线程知道第一个线程已经完成了它的操作。
   HANDLE CreateEvent(PSECURITY_ATTRIBUTS psa,
     bool fManualRest,
     bool fInitialState,
     PCTSTR pszname);

        第一个参数用来制定安全属性,通常我们用null,最后一个参数用来制定一个名字。fManualRest,参数是个布尔值,它能够告诉系统是创建一个人工重置的事件还是创建一个自动重置的事件(FALSE)。fInitialState,参数用于指明该事件是要初始化为已通知状态(TRUE)还是未通知状态(FALSE)。当系统创建事件对象后,createEvent就将与进程相关的句柄返回给事件对象。其他进程中的线程可以获得对该对象的访问权,方法是使用在pszName参数中传递的相同值,使用继承性,使用DuplicateHandle函数等来调用CreateEvent,或者调用OpenEvent,在pszName数中设定一个与调用CreateEvent时设定的名字相匹配的名字 。
        一旦事件已经创建,就可以直接控制它的状态。当调用SetEvent时,可以将事件改为已通知状态:当调用ResetEvent函数时,可以将该事件改为未通知状态。
        自动重置的事件与人工重置事件的区别在于,自动重置事件在线程成功地等待到该对象时,自动重置的事件就会自动重置到未通知状态。通常没有必要为自动重置的事件调用ResetEvent函数,因为系统会自动对事件进行重置

一个例子:

{
    作者:wudi_1982
    联系方式:wudi_1982@hotmail.com
    本代码只贴出了一些比较关键的部分
    转载请著名出处
}
 

// 利用事件对象演示同步的TThread派生类
  TEventThread = class (TThread)
    
private
      CurCount : integer;
// 当前计数
      Flabel : TLabel; // 显示当前计数的Tlabel组建
      procedure GetRestult;
    
protected
      procedure Execute;
override ;
    
public
       constructor Create(Alabel : TLabel);
  end;

var
 EventHandle : THandle;
// 事件对象的句柄

// TEventThread的实现代码

{ TEventThread }

constructor TEventThread.Create(Alabel: TLabel);
begin
  Flabel :
=  Alabel;
  inherited Create(False);
end;

procedure TEventThread.Execute;
var
 i : integer;
begin
  inherited;
  CurCount :
=   0 ;
  
for  i : =   0  to  10000   do
  begin
    
case  WaitForSingleObject(EventHandle, 5000 ) of
       WAIT_OBJECT_0 : begin
// 如果等待到事件对象,则将当前技术加1,并且显示在指定label上
         Inc(CurCount);
         Synchronize(GetRestult);
         Sleep(
0 );
          
// 关于sleep,switchtoThread的使用前面已经说了
         
//  SwitchToThread
          
// Application.ProcessMessages;
       end; // WAIT_OBJECT_0
       WAIT_TIMEOUT : begin // 超时则自动重置当前计数
         CurCount : =   0 ;
         Synchronize(GetRestult);
         SwitchToThread;
       end;
    end;
  end;
end;

procedure TEventThread.GetRestult;
begin
   Flabel.Caption :
=  IntToStr(CurCount);
end;

// 窗体单元中测试TEventThread的一些代码
procedure TForm1.CreateTClick(Sender: TObject);
begin
   
// 这里创建了TEventThread线程类的两个实例,目的是更好的演示自动重置和手动重置的区别
   TEventThread.Create(labEvent);
   TEventThread.Create(labEvent2);
end;

procedure TForm1.CreateEClick(Sender: TObject);
begin
  
// 生成一个事件对象,事件对象的重置方式以及初始化状态通过两个checkBox来决定
  EventHandle : =  CreateEvent(nil,ckbxAutoReset.Checked, ckbxInitEventState.Checked,pchar( ' MyEvent ' ));
end;

procedure TForm1.SetEClick(Sender: TObject);
begin
  
// 通知
  SetEvent(EventHandle);
end;

procedure TForm1.ResetEClick(Sender: TObject);
begin
  
// 未通知
  ResetEvent(EventHandle);
end;

procedure TForm1.closeClick(Sender: TObject);
begin
   
// 再你确定不需要此事件对象的时候,记得释放资源
   CloseHandle(EventHandle)
end;

 

利用事件对象演示同步的程序界面

利用事件对象演示线程同步的界面.

       整理上述代码,然后分别用不同的配置不同顺序的点击按钮,可以让你对事件对象的使用加深了解。

      内核方式的同步还有很多种,但原理基本都一样,后续文章对尽可能的依次举例列举。

参考文献:《WINDOWS核心编程》

   注:转载请著名出处,谢谢

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一、临界区 所谓临界区,就是一次只能由一个线程来执行的一段代码。如果把初始化数组的代码放在临界区内,另一个线程在第一个线程处理完之前是不会被执行的。 使用临界区的步骤: 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 GetLastErrorERROR_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 Lock3 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.

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值