首页 > 其他分享 >手搓了个读写锁...囧

手搓了个读写锁...囧

时间:2024-12-10 17:20:44浏览次数:3  
标签:... begin end Sender 读写 TObject FLocker procedure

业务需求, 适合需要使用读写锁进行控制, 印象里delphi一直都没有读写锁, 网上搜了搜也都是自己实现的

所以就手搓了一个, 搓完才发现, 系统自带了跨平台的高效读写锁 TLightweightMREW

留档存个念想吧.....附测试代码

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, System.SyncObjs,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

{$DEFINE SYS_RWLOCK}

type
  // 多读单写锁
  // 1.写的时候阻塞其他所有写和读
  // 2.读的时候不阻塞其他读,但阻塞所有写,当阻塞了一个或以上的写后,将阻塞所有后来新的读
  // 3.同线程写锁可重入
  TReadWriteLocker = class
  protected
    [Volatile]
    FLocker: Cardinal;
    FWriteThreadID: TThreadID;
  public
    procedure LockRead;
    procedure UnLockRead; inline;
    procedure LockWrite;
    procedure UnLockWrite; inline;
    function TryLockRead: Boolean; inline;
    function TryLockWrite: Boolean; inline;
    constructor Create;
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    Button6: TButton;
    Button7: TButton;
    Button8: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure Button5Click(Sender: TObject);
    procedure Button6Click(Sender: TObject);
    procedure Button7Click(Sender: TObject);
    procedure Button8Click(Sender: TObject);
  private
    { Private declarations }
    FLock: {$IFDEF SYS_RWLOCK}TLightweightMREW{$ELSE}TReadWriteLocker{$ENDIF};
    FStop: Boolean;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TReadWriteLocker }

procedure TReadWriteLocker.LockRead;
var
  lCurLock: Integer;
  lWait: TSpinWait;
begin
  {如果有同线程的写锁未释放, 会导致死锁}
  if TThread.Current.ThreadID = FWriteThreadID then
    raise Exception.Create('当前线程有未释放的写锁');

  lWait.Reset;
  while True do
  begin
    lCurLock := FLocker;

    {没有写锁时, 累加读锁}
    if lCurLock <= $FFFF then
    begin
      if TInterlocked.CompareExchange(FLocker, lCurLock + 1, lCurLock) = lCurLock then
        Exit;
    end;
    lWait.SpinCycle;
  end;
end;

procedure TReadWriteLocker.LockWrite;
var
  lWait: TSpinWait;
  lCurrTID: TThreadID;
begin
  lCurrTID := TThread.Current.ThreadID;
  lWait.Reset;

  {只有同线程才能累加写锁数量, 非同线程只通过锁进行互斥
  而同线程不会出现并发, 所以无论是否有写锁存在, 异步线程一定会进入循环判断
  所以先上锁后记录写锁线程, 不会引发冲突}
  if FWriteThreadID <> lCurrTID then
  begin
    while TInterlocked.CompareExchange(FLocker, $10000, 0) <> 0 do
      lWait.SpinCycle;
    FWriteThreadID := lCurrTID;
  end
  else
  begin
    {同线程的写锁, 一定不会出现并发, 所以无需原子操作}
    if FLocker and $FFFF0000 = $FFFF then
      raise Exception.Create('写锁超出上限');
    FLocker := FLocker + $10000;
  end;
end;

function TReadWriteLocker.TryLockRead: Boolean;
var
  lCurLock: Integer;
begin
  Result := False;

  {如果有同线程的写锁未释放, 会导致死锁}
  if TThread.Current.ThreadID = FWriteThreadID then
    Exit;

  lCurLock := FLocker;

  {没有写锁时, 累加读锁}
  if lCurLock > $FFFF then
    Exit;

  Result := TInterlocked.CompareExchange(FLocker, lCurLock + 1, lCurLock) = lCurLock;
end;

function TReadWriteLocker.TryLockWrite: Boolean;
var
  lCurrTID: TThreadID;
begin
  Result := False;

  lCurrTID := TThread.Current.ThreadID;

  if FWriteThreadID <> lCurrTID then
  begin
    Result := TInterlocked.CompareExchange(FLocker, $10000, 0) = 0;
    if Result then
      FWriteThreadID := lCurrTID;
  end
  else
  begin
    if FLocker and $FFFF0000 = $FFFF then
      raise Exception.Create('写锁超出上限');
    FLocker := FLocker + $10000;
    Result := True;
  end;
end;

procedure TReadWriteLocker.UnLockWrite;
var
  lCurrTID: TThreadID;
begin
  lCurrTID := TThread.Current.ThreadID;
  if FWriteThreadID <> lCurrTID then
    raise Exception.Create('写锁不属于当前线程');

  if FLocker < $10000 then
    raise Exception.Create('未进入写锁');

  {最后一次解锁, 将写线程ID归0, 由于写锁全互斥, 所以无需考虑并发}
  if FLocker and $FFFF0000 = $10000 then
    FWriteThreadID := 0;

  FLocker := FLocker - $10000;
end;

procedure TReadWriteLocker.UnLockRead;
begin
  TInterlocked.Decrement(FLocker);
end;

constructor TReadWriteLocker.Create;
begin
  FLocker := 0;
  FWriteThreadID := 0;
end;


procedure TForm1.Button1Click(Sender: TObject);
begin
  FLock.{$IFDEF SYS_RWLOCK}BeginWrite{$ELSE}LockWrite{$ENDIF};
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  FLock.{$IFDEF SYS_RWLOCK}BeginRead{$ELSE}LockRead{$ENDIF};
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  FLock.{$IFDEF SYS_RWLOCK}EndWrite{$ELSE}UnLockWrite{$ENDIF};
end;

procedure TForm1.Button4Click(Sender: TObject);
begin
  FLock.{$IFDEF SYS_RWLOCK}EndRead{$ELSE}UnLockRead{$ENDIF};
end;

procedure TForm1.Button5Click(Sender: TObject);
begin
  TThread.CreateAnonymousThread(
    procedure
    var
      lD: TDateTime;
    begin
      lD := Now;
      FLock.{$IFDEF SYS_RWLOCK}BeginRead{$ELSE}LockRead{$ENDIF};
      try
        MessageBox(0, PChar('read - ' + FormatDateTime('nn:ss.zzz', lD)), '', mb_ok);
      finally
        FLock.{$IFDEF SYS_RWLOCK}EndRead{$ELSE}UnLockRead{$ENDIF};
      end;
    end
  ).Start;
end;

procedure TForm1.Button6Click(Sender: TObject);
begin
  TThread.CreateAnonymousThread(
    procedure
    var
      lD: TDateTime;
    begin
      lD := Now;
      FLock.{$IFDEF SYS_RWLOCK}BeginWrite{$ELSE}LockWrite{$ENDIF};
      try
//        if FLock.{$IFDEF SYS_RWLOCK}TryBeginWrite{$ELSE}TryLockWrite{$ENDIF} then
        begin
          MessageBox(0, PChar('write - ' + FormatDateTime('nn:ss.zzz', lD)), '', mb_ok);
//          FLock.{$IFDEF SYS_RWLOCK}EndWrite{$ELSE}UnLockWrite{$ENDIF};
        end;
      finally
        FLock.{$IFDEF SYS_RWLOCK}EndWrite{$ELSE}UnLockWrite{$ENDIF};
      end;
    end
  ).Start;
end;

procedure TForm1.Button7Click(Sender: TObject);
begin
  FStop := False;
  TThread.CreateAnonymousThread(
    procedure
    begin
      while not FStop do
      begin
        Sleep(1);
        if not FLock.{$IFDEF SYS_RWLOCK}TryBeginRead{$ELSE}TryLockRead{$ENDIF} then
          Continue;
        Sleep(1);
        FLock.{$IFDEF SYS_RWLOCK}EndRead{$ELSE}UnLockRead{$ENDIF};
      end;
    end
  ).Start;
end;

procedure TForm1.Button8Click(Sender: TObject);
begin
  FStop := True;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  {$IFNDEF SYS_RWLOCK}FLock := TReadWriteLocker.Create{$ENDIF};
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  {$IFNDEF SYS_RWLOCK}FLock.Free{$ENDIF};
end;

end.

 

标签:...,begin,end,Sender,读写,TObject,FLocker,procedure
From: https://www.cnblogs.com/lzl_17948876/p/18597718

相关文章

  • 部署达梦8读写分离集群
    一、原理读写分离集群其实是主备集群的升级版本,在双机热备的基础上把用户写和读的会话事务分别放在主库和备库上执行。写事务会被分发到主库上执行,读事务则部分分发到备库上执行,分发比例在dm_svc.conf中控制,如:RW_PERCENT=25,指的是25%的读分发到主库。由接口负责读写事务的分发......
  • 【源码】Sharding-JDBC源码分析之SQL中读写分离动态策略、数据库发现规则及DatabaseDi
     Sharding-JDBC系列1、Sharding-JDBC分库分表的基本使用2、Sharding-JDBC分库分表之SpringBoot分片策略3、Sharding-JDBC分库分表之SpringBoot主从配置4、SpringBoot集成Sharding-JDBC-5.3.0分库分表5、SpringBoot集成Sharding-JDBC-5.3.0实现按月动态建表分表6、【源码......
  • 【Linux内核】4张IO时序图,一次搞懂Linux下的文件读写
    因为如今大多数资源都是通过网络访问的:数据库、对象存储和其他微服务。大多数服务器应用程序开发人员在考虑I/O时,都会考虑网络I/O,然而,数据库开发人员还必须考虑文件I/O。一般来说,在Linux服务器上访问文件有四种选择:传统读/写、mmap、直接I/O(DIO)读/写和异步直接I/O(AIO/DIO)。......
  • 【Fiddler】iOS抓取全部显示“Tunnel to......443”
     前面的流程正常配置 IOS下载证书:IP+端口--192.168.XX.XX:8888正常情况能抓取,如果出现无法抓取到https请求,显示http灰锁,报443。 fiddler中log报错:由于远程方已关闭传输流,身份验证失败。解决方法(原因-默认的证书不符合Android和iOS的证书要求): (1)将电脑端中的证书,使......
  • python中的defaultdict([default_factory[, ...]])方法
    这个方法在_collection.py中的介绍如下:'''defaultdict(default_factory=None,/,[...])-->dictwithdefaultfactoryThedefaultfactoryiscalledwithoutargumentstoproduceanewvaluewhenakeyisnotpresent,in__getitem__only.Adefaultdict......
  • Android14 关于读写权限 (Vivo)
    按常理来讲,在相机业务,或从相册读取图片时,应该要申请读写权限,在使用Delphi12+Android14环境下,发现在申请读写权限时,申请权限内容中,这二项不需要授权?不清楚是自己处理的问题,还是规则有所变更>>申请权限>>相机申请三项权限,无论怎么操作,都只有相机权限一项返回(关于读写......
  • 集成minio启动报错:Caused by:java.lang.IllegalArgumentException:invalid hostname 1
    ......
  • 异常处理try...except的应用
    '''try...except语法应用出现问题一般两种提示:1.Error(错误):一般是语法导致的问题,运算逻辑出现问题,都会在控制台以Error形态展示。可以通过Error在控制台的相关信息快速去排查定位缺陷的所在点2.Exception(异常):一般是程序运行时,由于环境导致的问题。由于数据传递出......
  • 工作六年,再看到这样的代码,内心五味杂陈......
    那天下午,看到了令我终生难忘的代码,那一刻破防了…Java学习包传送门故事还得从半年前数据隔离的那个事情说起…1历史背景1.1数据隔离预发,灰度,线上环境共用一个数据库。每一张表有一个env字段,环境不同值不同。特别说明:env字段即环境字段。如下图所示:1.2隔离之前插......
  • 【Linux】多线程(自旋锁、读写锁)
     ......