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)来关闭互斥对象。请注意例程中该函数的使用位置。

时间: 2024-10-12 11:35:13

Delphi多线程编程之同步读写全局数据的相关文章

Linux程序设计学习笔记----多线程编程线程同步机制之互斥量(锁)与读写锁

互斥锁通信机制 基本原理 互斥锁以排他方式防止共享数据被并发访问,互斥锁是一个二元变量,状态为开(0)和关(1),将某个共享资源与某个互斥锁逻辑上绑定之后,对该资源的访问操作如下: (1)在访问该资源之前需要首先申请互斥锁,如果锁处于开状态,则申请得到锁并立即上锁(关),防止其他进程访问资源,如果锁处于关,则默认阻塞等待. (2)只有锁定该互斥锁的进程才能释放该互斥锁. 互斥量类型声明为pthread_mutex_t数据类型,在<bits/pthreadtypes.h>中有具体的定义. 互斥量

delphi 多线程编程

开始本应该是一篇洋洋洒洒的文字, 不过我还是提倡先做起来, 在尝试中去理解.先试试这个: procedure TForm1.Button1Click(Sender: TObject); var i: Integer; begin for i := 0 to 500000 do begin Canvas.TextOut(10, 10, IntToStr(i)); end; end; 上面程序运行时, 我们的窗体基本是 "死" 的, 可以在你在程序运行期间拖动窗体试试... Delphi

多线程编程-----线程同步

同步,永远是多线程编程中最核心和最重要的话题.同步相关的概念比如:临界区,原子操作,以及互斥量等等 总的来说,在多个线程之间采取同步措施,无非是为了让他们更好的协同工作或者维持共享数据的一致性. 1.共享数据的一致性: 多线程程序多以共享数据作为在线程之间传递数据的手段,由于一个进程所拥有的相当一部分虚拟内存地址都可以被该线程中的所有线程所共享.因此这些被共享的数据大多也已内存空间作为载体. 实际上,保证共享数据一致性的最简单且最好的方法,就是使得该数据成为一个常量,但是如果把一个计数器做成常量

java多线程编程——线程同步之同步函数

如何找出线程安全问题: 1.明确那些代码块是多线程运行代码 2.明确共享数据 3.明确多线程运行代码中哪些语句是操作共享数据的 同步函数示例: class Save{ private int sum; public synchronized void add(int n){ sum+=n; System.out.println("sum="+sum); } } class Cus implements Runnable{ private Save b=new Save(); publi

Delphi多线程编程(8)--多线程同步之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

Delphi多线程编程(12)--多线程同步之Semaphore(信号对象)

转载自:万一的博客 之前已经有了两种线程同步的方法: CriticalSection(临界区)和Mutex(互斥)吗,这两种同步方法差不多,只是作用域不同 CriticalSection类似于只有一个蹲位的公共厕所,只能一个个地进 Mutex 对象类似于接力赛中的接力棒,某一时刻只能有一个人持有,谁拿着谁跑 什么是Semaphore(信号或叫信号量)呢? 譬如到银行办业务.或者到车站买票,原来只有一个服务员,不管有多少人排队等候,业务只能一个个地来 假如增加业务窗口,可以同时受理几个业务呢? 这

Delphi多线程编程(10)--多线程同步之Mutex(互斥对象)

原理分析: 互斥对象是系统内核对象,各个线程都可以拥有它,谁拥有它谁就可以执行 执行完毕,用ReleaseMutex 函数释放拥有权,以让其他等待的线程可以使用 其他线程可以使用 WaitForSingleObject函数排队等待(等待也可以理解为排队申请) 使用过程 var hMutex: THandle; {应该先声明一个全局的互斥句柄} CreateMutex {建立互斥对象} WaitForSingleObject {用等待函数排队等候} ReleaseMutex {释放拥有权} Clo

Delphi多线程编程(14)--多线程同步之WaitableTimer(等待定时器对象)

转载自:万一的博客 function CreateWaitableTimer( lpTimerAttributes: PSecurityAttributes; //安全 bManualReset: BOOL; //True:可调度多个线程:False:只调度一个线程 lpTimerName: PWideChar //名称 ):THandle; stdcall; //返回句柄 function SetWaitableTimer( hTime: THandle; //句柄 var lpDueTime

Delphi多线程编程(15)--多线程同步之WaitableTimer(等待定时器对象)[续]

转载自:万一的博客 本次专门研究下 SetWaitableTimer的第二个参数(起始时间) 它有正值.负值.0值这三种情况,前面已经用过 0 值 先学习负值(相对时间),也就是从当前算起隔多长时间开始执行 这个相对时间是以 1/100 纳秒为单位的,譬如赋值:3*10000000 相当于 3 秒 1 s(秒) = 1,000            ms(毫秒); 1 s(秒) = 1,000,000        µs(微妙); 1 s(秒) = 1,000,000,000    ns(纳秒)