zoukankan      html  css  js  c++  java
  • Delphi多线程编程之同步读写全局数据

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


     

    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.

    没有临界区和互斥的帮助,两个线程都不断地在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、声明一个类型为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)来关闭互斥对象。请注意例程中该函数的使用位置。

  • 相关阅读:
    C#调用java类、jar包方法
    利用IKVM在C#中调Java程序
    同样版本的jstl,都是jstl1.2版本,有个有问题,另一个没有问题
    Python安装及开发环境配置
    jQuery EasyUI parser 的使用场景
    关于HttpURLConnection.setFollowRedirects
    HttpUrlConnection 的 setDoOutput 与 setDoInput
    【Mybatis】向MySql数据库插入千万记录 单条插入方式,用时 1h16m30s
    [MyBatis]再次向MySql一张表插入一千万条数据 批量插入 用时5m24s
    [MyBatis]向MySql数据库插入一千万条数据 批量插入用时6分 之前时隐时现的异常不见了
  • 原文地址:https://www.cnblogs.com/plug/p/4557213.html
Copyright © 2011-2022 走看看