我一直在使用一些多线程应用程序,其中一部分需要线程保护对象。我使用以下方法保护单个对象线程保护:
type
TMyClass = class(TObject)
private
FLock: TRTLCriticalSection;
FSomeString: String;
procedure Lock;
procedure Unlock;
function GetSomeString: String;
procedure SetSomeString(Value: String);
public
constructor Create;
destructor Destroy; override;
property SomeString: String read GetSomeString write SetSomeString;
end;
implementation
constructor TMyClass.Create;
begin
InitializeCriticalSection(FLock);
Lock;
try
//Initialize some stuff
finally
Unlock;
end;
end;
destructor TMyClass.Destroy;
begin
Lock;
try
//Finalize some stuff
finally
Unlock;
end;
DeleteCriticalSection(FLock);
inherited Destroy;
end;
procedure TMyClass.Lock;
begin
EnterCriticalSection(FLock);
end;
procedure TMyClass.Unlock;
begin
LeaveCriticalSection(FLock);
end;
function TMyClass.GetSomeString: String;
begin
Result:= '';
Lock;
try
Result:= FSomeString;
finally
Unlock;
end;
end;
procedure TMyClass.SetSomeString(Value: String);
begin
Lock;
try
FSomeString:= Value;
finally
Unlock;
end;
end;
但是,当我实现一个对象列表时,我无法弄清楚如何安全地保护每个对象。我创建了这样的对象列表:
type
TMyClass = class;
TMyClasses = class;
TMyClass = class(TObject)
private
FOwner: TMyClasses;
public
constructor Create(AOwner: TMyClasses);
destructor Destroy; override;
end;
TMyClasses = class(TObject)
private
FItems: TList;
function GetMyItem(Index: Integer): TMyItem;
public
constructor Create;
destructor Destroy; override;
procedure Clear;
function Count: Integer;
property Items[Index: Integer]: TMyClass read GetMyItem; default;
end;
implementation
{ TMyClass }
constructor TMyClass.Create(AOwner: TMyClasses);
begin
FOwner:= AOwner;
FOwner.FItems.Add(Self);
//Initialize some stuff...
end;
destructor TMyClass.Destroy;
begin
//Uninitialize some stuff...
inherited Destroy;
end;
{ TMyClasses }
constructor TMyClasses.Create;
begin
FItems:= TList.Create;
end;
destructor TMyClasses.Free;
begin
Clear;
FItems.Free;
inherited Destroy;
end;
procedure TMyClasses.Clear;
begin
while FItems.Count > 0 do begin
TMyClass(FItems[0]).Free;
FItems.Delete(0);
end;
end;
function TMyClasses.Count: Integer;
begin
Result:= FItems.Count;
end;
function TMyClasses.GetMyItem(Index: Integer): TMyClass;
begin
Result:= TMyClass(FItems[Index]);
end;
我认为有两种方法可以做到这一点,而且我不信任这两种方式。一种方法是在列表对象(TMyClasses
)中实现关键部分锁定,并且其中的每个对象将共享此锁定(通过调用FOwner.Lock;
和FOwner.Unlock;
。但是然后两个不同的线程将不会甚至可以同时使用此列表中的两个不同对象,并且会破坏多线程的目的。第二种方法是将另一个关键部分放在他们自己的每个单独对象中,但是其中太多是也很危险,对吗?如何保护列表和列表中的每个对象?
答案 0 :(得分:1)
您无法真实地期望在列表类中使用与在序列化对单个对象的访问的简单类中使用相同的方法。
例如,您的列表类与之前的许多属性一样,具有Count
属性和索引Items[]
属性。我将假设你的线程模型允许列表变异。现在,假设你想编写这样的代码:
for i := 0 to List.Count-1 do
List[i].Frob;
假设另一个线程在此循环运行时改变列表。那么,这显然会导致运行时故障。因此,我们可以得出结论,上面的循环需要用锁包装。这意味着列表的线程安全方面必须在外部公开。你无法将它全部保留在当前的设计中。
如果您希望将锁定保留在课程内部,则必须删除Count
和Items[]
属性。你可以让你的列表看起来像这样(删除了一些部分):
type
TThreadsafeList<T> = class
private
FList: TList<T>;
procedure Lock;
procedure Unlock
public
procedure Walk(const Visit: TProc<T>);
end;
....
procedure TThreadsafeList<T>.Walk(const Visit: TProc<T>);
var
Item: T;
begin
Lock;
try
for Item in FList do
Visit(Item);
finally
Unlock;
end;
end;
现在你可以用以下代码替换上面的循环:
ThreadsafeList.Walk(
procedure(Item: TMyItemClass)
begin
Item.Frob;
end
);
根据Walk
程序的确定,扩展此概念以允许您的Visit
方法支持删除某些项目并不困难。
但正如你所说,你可以用这样的清单做的事情是没有实际意义的。共享数据是多线程的祸根。我建议你找到一种解决问题的方法,为每个线程提供所需数据的私有副本。此时你不需要同步,这一切都很好。
最后一点。线程安全没有单一的概念。线程安全的含义因环境而异。 Eric Lippert说得最好:What is this thing you call "thread safe"?所以当你提出这样的问题时,你应该详细说明你的特定用例和线程模型。