现代处理器均支持多线程操作,在程序运行中的并发请求,经常会消耗大量的数据库资源,而我们都直到数据库资源是很宝贵的,所以会将经常访问的资源做成缓存来减少数据库的访问;比如使用Redis等,但网络延迟等原因会造成并发堵塞;对于数据量小但访问量高的数据Redis并不是最优解; 内存表(TFDmemtable/TClientDataSet)效率高,但是操作并不是太方便;那只能自己写一个咯~
代码实现了一个线程安全的用户信息管理模块,核心目标是通过接口(IUser)自动管理用户对象(TUser)的生命周期,同时利用读写锁(TMREWSync)保证多线程场景下的并发安全(多读少写的应用场景效率很高),支持用户的添加、删除、查询、更新和 JSON 枚举等操作。关于数据库的部分,因为各个系统的特色自行添加即可~
unit uManager_User;
interface
uses
System.SysUtils, System.Generics.Collections, System.Rtti, qjson ;
type
/// <summary> 用户参数 </summary>
TUserParams = Record
key : string; //必须且固定的字段
name: string;
age: Integer;
// 初始化空记录
procedure InitEmpty;
End;
/// --------所有操作通过 IUser 接口或 TUserManager 方法完成---------
/// <summary> 用户接口(核心:引用计数管理生命周期) </summary>
IUser = interface
['{5E8A1B2C-3D4E-5F6A-7B8C-9D0E1F2A3B4C}']
function GetKey: string;
function GetName: string;
function GetAge: Integer;
// 暴露参数记录(方便外部获取完整数据)
function GetParams: TUserParams;
procedure UpdateInfo(AParams: TUserParams);
property key: string read GetKey;
property name: string read GetName;
property age: Integer read GetAge;
end;
/// <summary> 用户对象(实现接口,自动引用计数) </summary>
/// <summary> TUser 由 IUser进行自动管理,不可引用或手动释放(free) </summary>
TUser = class(TInterfacedObject, IUser)
private
FUserRecord : TUserParams;
function GetKey: string;
function GetName: string;
function GetAge: Integer;
public
constructor Create(AParams:TUserParams);
function GetParams: TUserParams;
procedure UpdateInfo(AParams:TUserParams);
end;
/// <summary> 用户管理对象 </summary>
TUserManager = class(TObject)
private
/// <summary> 泛型字典类(引用计数管理,不需要手动释放对象)</summary>
FDict: TDictionary<string, IUser>;
/// <summary> 读写锁同步对象; </summary>
/// <summary> 适用于读多写少的场景;可支持高并发读取; </summary>
FMREW: TMREWSync;
public
/// <summary> 管理对象创建 </summary>
constructor Create;
/// <summary> 管理对象释放(重载) </summary>
destructor Destroy; override;
/// <summary> 对象引用清空(不释放对象) </summary>
/// <returns> 无 </returns>
procedure Clear;
/// <summary> 获取对象数量 </summary>
/// <returns> Integer - 返回对象数量 </returns>
function Count: Integer;
/// <summary> 添加对象 </summary>
/// <param name="AParams"> 对象参数 </param>
/// <returns> 无 </returns>
procedure AddUser(AParams : TUserParams);
/// <summary> 移除对象 </summary>
/// <param name="AKey"> 主键值 </param>
/// <returns> Boolean - True:成功 False:失败 </returns>
function RemoveUser(AKey: string): Boolean;
/// <summary> 获取对象 - 根据主键值 </summary>
/// <param name="AKey"> 主键值 </param>
/// <returns> IUser - 对象(接口) 外部访问安全(自动管理生命周期) </returns>
function GetUser(AKey: string): IUser;
/// <summary> 获取对象 - 根据索引号 </summary>
/// <param name="AIndex"> 索引号 </param>
/// <returns> IUser - 对象(接口) 外部访问安全(自动管理生命周期) </returns>
function GetUserByIndex(AIndex : integer): IUser;
/// <summary> 枚举对象 </summary>
/// <returns> string - Json字符串(数组) </returns>
function EnumerateUsers:string;
end;
implementation
{ TUserParams }
procedure TUserParams.InitEmpty;
begin
key := '';
name := '';
age := 0;
end;
{ TUser }
constructor TUser.Create(AParams:TUserParams);
begin
inherited Create;
FUserRecord := AParams;
end;
function TUser.GetKey: string;
begin
Result := FUserRecord.key;
end;
function TUser.GetName: string;
begin
Result := FUserRecord.name;
end;
function TUser.GetAge: Integer;
begin
Result := FUserRecord.age;
end;
function TUser.GetParams: TUserParams;
begin
Result := FUserRecord;
end;
procedure TUser.UpdateInfo(AParams: TUserParams);
begin
if (AParams.key = '') or (FUserRecord.key <> AParams.key) then Exit;
FUserRecord.name := AParams.name;
FUserRecord.age := AParams.age;
end;
{ TUserManager }
constructor TUserManager.Create;
begin
inherited;
FMREW := TMREWSync.Create;
// 改为普通字典,不再托管值的释放(由接口引用计数管理)
FDict := TDictionary<string, IUser>.Create;
end;
destructor TUserManager.Destroy;
begin
//字典释放仅移除接口引用,对象由引用计数自动销毁
FreeAndNil(FDict);
FreeAndNil(FMREW);
inherited;
end;
procedure TUserManager.Clear;
begin
FMREW.BeginWrite;
try
// 移除所有接口引用,无引用的对象会自动销毁
FDict.Clear;
finally
FMREW.EndWrite;
end;
end;
function TUserManager.Count: Integer;
begin
FMREW.BeginRead;
try
Result := FDict.Count;
finally
FMREW.EndRead;
end;
end;
procedure TUserManager.AddUser(AParams : TUserParams);
var
_User: IUser;
begin
if AParams.key = '' then Exit;
FMREW.BeginWrite;
try
if FDict.TryGetValue(AParams.key, _User) then
begin
_User.UpdateInfo(AParams); // 存在则更新
end
else
begin
_User := TUser.Create(AParams); // 接口自动管理引用计数
FDict.Add(AParams.key, _User);
end;
finally
FMREW.EndWrite;
end;
end;
function TUserManager.RemoveUser(AKey: string): Boolean;
var
_IUser : IUser;
begin
Result := False;
if AKey = '' then Exit;
FMREW.BeginWrite;
try
if FDict.TryGetValue(AKey, _IUser) then
begin
// 仅移除字典中的接口引用,对象是否销毁由外部引用数决定
FDict.Remove(AKey);
Result := True;
end
else
begin
//不存在直接返回成功
Result := True;
end;
finally
FMREW.EndWrite;
end;
end;
function TUserManager.GetUser(AKey: string): IUser;
begin
Result := nil;
if AKey = '' then Exit;
FMREW.BeginRead;
try
// 返回对象接口,引用计数+1
FDict.TryGetValue(AKey, Result);
finally
FMREW.EndRead;
end;
end;
function TUserManager.GetUserByIndex(AIndex: integer): IUser;
var
_Pair: TPair<string, IUser>;
_iIndex : integer;
begin
Result := nil;
if AIndex < 0 then Exit;
FMREW.BeginRead;
try
if AIndex >= FDict.Count then Exit;
_iIndex := 0;
for _Pair in FDict do
begin
if _iIndex = AIndex then
begin
// 返回对象接口,引用计数+1
Result := _Pair.Value;
Break;
end;
inc(_iIndex);
end;
finally
FMREW.EndRead;
end;
end;
function TUserManager.EnumerateUsers: string;
var
_Pair: TPair<string, IUser>;
_QJson, _ItemJson: TQJson;
begin
Result := '[]'; // 默认返回空JSON数组
_QJson := TQJson.Create;
try
_QJson.DataType := jdtArray;
FMREW.BeginRead;
try
for _Pair in FDict do
begin
_ItemJson := TQJson.Create;
_ItemJson.FromRecord(_Pair.Value.GetParams); // 序列化接口指向的对象
_QJson.add(_ItemJson);
end;
Result := _QJson.AsString;
finally
FMREW.EndRead;
end;
finally
if Assigned(_QJson) then FreeAndNil(_QJson);
end;
end;
end.Pascal使用方法如下:
uses
uManager_User;
var
FUserManager : TUserManager;
_UserParams : TUserParams ;
_IUser : IUser;
begin
FUserManager := TUserManager.Create;
try
try
//添加
_UserParams.key := '20';
_UserParams.Name := 'test';
_UserParams.Age := 34;
FUserManager.AddUser(_UserParams);
//读取
_IUser := FUserManager.GetUser('20');
if Assigned(_IUser) then
begin
_IUser.key;
_IUser.Name;
_IUser.Age;
end;
//移除
FUserManager.RemoveUser('20');
//清空
FUserManager.Clear;
except
on E: Exception do
begin
//记录日志
end
end
finally
if Assigned(FUserManager) then FreeAndNil(FUserManager);
end;
end;Pascal多线程测试:
//写入线程
TThread.CreateAnonymousThread(
procedure
var
I : integer;
F : TProc<integer>;
begin
Randomize;
For I := 1 to 1500 do
begin
F := procedure(i:integer)
begin
TTask.Run(
procedure
var
J : integer;
_UserParams : TUserParams;
begin
J := Random(20);
sleep(J * 100);
_UserParams.key := inttostr(J);
_UserParams.Name := 'test' + inttostr(I);
_UserParams.Age := I;
FUserManager.AddUser(_UserParams);
TInterlocked.Increment(iCount_Complete);
end);
end;
F(i);
end;
iCount_All := iCount_All + I - 1;
end).Start;
//读取线程
TThread.CreateAnonymousThread(
procedure
var
I : integer;
F : TProc<integer>;
begin
Randomize;
For I := 1 to 20000 do
begin
F := procedure(i:integer)
begin
TTask.Run(
procedure
var
J : integer;
_IUser : IUser;
begin
J := Random(20);
sleep(J);
_IUser := FUserManager.GetUser(inttostr(J));
if assigned(_IUser) then
begin
_IUser.key;
_IUser.name;
_IUser.age;
end;
TInterlocked.Increment(iCount_Complete);
end);
end;
F(i);
end;
iCount_All := iCount_All + I - 1;
end).Start;
//删除线程
TThread.CreateAnonymousThread(
procedure
var
I : integer;
F : TProc<integer>;
begin
Randomize;
For I := 1 to 1000 do
begin
F := procedure(i:integer)
begin
TTask.Run(
procedure
var
J : integer;
begin
J := Random(20);
sleep(J);
FUserManager.RemoveUser(inttostr(J));
TInterlocked.Increment(iCount_Complete);
end);
end;
F(i);
end;
iCount_All := iCount_All + I - 1;
end).Start;Pascal以上代码在开始执行时,因为有新增和删除,存在写入锁读取执行较慢~ 后面无新增和删除时,读取速度就起来了~ 并且无内存泄漏;如果有更好的方法,请联系我~ 342667266@qq.com
