1

我一直在使用一些多线程应用程序,其中一部分需要线程保护对象。我使用以下方法对单个对象线程进行了保护:

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;一次,并且会破坏多线程的目的。第二种方法是在每个单独的对象中放置另一个关键部分,但是太多也很危险,对吧?我怎样才能保护列表和其中的每个对象清单在一起?

4

1 回答 1

1

您实际上不能期望在列表类中使用与在序列化对单个对象的访问的简单类中使用的方法相同的方法。

例如,您的列表类与之前的许多类一样具有一个Count属性和一个索引Items[]属性。我将假设您的线程模型允许列表发生变异。现在,假设您想编写如下代码:

for i := 0 to List.Count-1 do
  List[i].Frob;

假设另一个线程要在此循环运行时改变列表。好吧,这显然会导致运行时失败。因此,我们可以得出结论,上面的循环需要用锁包裹。这意味着列表的线程安全方面必须暴露在外部。您不能将其全部保留在当前设计中。

如果您希望将锁保留在类内部,则必须删除CountandItems[]属性。你可以让你的列表看起来像这样(删除了一些部分):

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 说得最好:你称之为“线程安全”的东西是什么?因此,每当您提出这样的问题时,您都应该提供有关您的特定用例和线程模型的大量细节。

于 2012-11-26T21:40:35.483 回答