热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Delphi多线程编程之同步读写全局数据

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

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


 

 

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


推荐阅读
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • 有没有一种方法可以在不继承UIAlertController的子类或不涉及UIAlertActions的情况下 ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • Android源码中的Builder模式及其作用
    本文主要解释了什么是Builder模式以及其作用,并结合Android源码来分析Builder模式的实现。Builder模式是将产品的设计、表示和构建进行分离,通过引入建造者角色,简化了构建复杂产品的流程,并且使得产品的构建可以灵活适应变化。使用Builder模式可以解决开发者需要关注产品表示和构建步骤的问题,并且当构建流程发生变化时,无需修改代码即可适配新的构建流程。 ... [详细]
  • 抽空写了一个ICON图标的转换程序
    抽空写了一个ICON图标的转换程序,支持png\jpe\bmp格式到ico的转换。具体的程序就在下面,如果看的人多,过两天再把思路写一下。 ... [详细]
author-avatar
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有