我已经构建了一个简单的日志记录类,并希望确认它是线程安全的。基本上,Log
,RegisterLogger
和UnRegisterLogger
将从不同的线程调用。 Log
将很多(来自许多不同的主题)和RegisterLogger
和UnRegisterLogger
很少被调用。
基本上我的问题可归结为:“TList<x>
线程读取是否安全?”,也就是说我可以让多个线程同时访问TList
。
IExecutionCounterLogger
是一个带有Log方法的接口(与TExecutionCounterServer.Log
具有相同的签名)
Type
TExecutionCounterServer = class
private
Loggers : TList<IExecutionCounterLogger>;
Synchronizer : TMultiReadExclusiveWriteSynchronizer;
public
procedure RegisterLogger(Logger : IExecutionCounterLogger);
procedure UnRegisterLogger(Logger : IExecutionCounterLogger);
procedure Log(const ClassName, MethodName : string; ExecutionTime_ms : integer);
constructor Create;
destructor Destroy; override;
end;
constructor TExecutionCounterServer.Create;
begin
Loggers := TList<IExecutionCounterLogger>.Create;
Synchronizer := TMultiReadExclusiveWriteSynchronizer.Create;
end;
destructor TExecutionCounterServer.Destroy;
begin
Loggers.Free;
Synchronizer.Free;
inherited;
end;
procedure TExecutionCounterServer.Log(const ClassName, MethodName: string; ExecutionTime_ms: integer);
var
Logger: IExecutionCounterLogger;
begin
Synchronizer.BeginRead;
try
for Logger in Loggers do
Logger.Log(ClassName, MethodName, ExecutionTime_ms);
finally
Synchronizer.EndRead;
end;
end;
procedure TExecutionCounterServer.RegisterLogger(Logger: IExecutionCounterLogger);
begin
Synchronizer.BeginWrite;
try
Loggers.Add(Logger);
finally
Synchronizer.EndWrite;
end;
end;
procedure TExecutionCounterServer.UnRegisterLogger(Logger: IExecutionCounterLogger);
var
i : integer;
begin
Synchronizer.BeginWrite;
try
i := Loggers.IndexOf(Logger);
if i = -1 then
raise Exception.Create('Logger not present');
Loggers.Delete(i);
finally
Synchronizer.EndWrite;
end;
end;
作为更多背景,这是this question的后续内容。基本上我已经为(DCOM)DataSnap服务器的每个方法添加了一些工具,我也已经连接到每个TDataSnapProvider OnGetData和OnUpdateData事件。
答案 0 :(得分:8)
TList<T>
线程上的读取是否安全?也就是说我可以让多个线程同时访问TList<T>
吗?
这是线程安全的,不需要同步。多个线程可以安全地同时读取。这相当于(并且实际上实现为)从数组中读取。只有当您的某个线程修改了需要同步的列表时。
您的代码比此方案稍微复杂一些。您似乎需要满足修改列表的线程。但是你已经使用TMultiReadExclusiveWriteSynchronizer
这样做了,这是一个非常好的解决方案。它允许多个读取线程同时操作,但任何写入线程都相对于所有其他线程进行序列化。
答案 1 :(得分:2)
强调问题的第一部分,您声明对RegisterLogger和UnregisterLogger的调用很少。虽然Log调用只是读取列表,但其他两个正在更改列表。在这种情况下,您必须确保在执行或可能发生日志调用时没有执行这些操作。
想象一下,在Log中的for循环期间执行UnregisterLogger中的Delete。结果至少是不可预测的。
仅在这两次写入调用中使用同步器是不够的。
所以问题的答案
TList线程上的读取是否安全?
只能是:它取决于!
如果可以确保没有RegisterLogger和UnregisterLogger发生(即只能进行读取调用),则可以安全地省略Synchronizer。否则 - 最好不要。