Delphi - TDictionary线程是否安全

时间:2014-12-17 01:41:48

标签: multithreading delphi tcp thread-safety indy

我的想法是使用TDictionary来管理IdTCPServer上的客户端连接。以下是用于理解目的的简单示例代码(未经测试):

var
  Dic: TDictionary<string, TIdContext>;

procedure TfrmMain.FormCreate(Sender: TObject);
begin
  Dic := TDictionary<string, TIdContext>.Create;
end;

procedure TfrmMain.FormDestroy(Sender: TObject);
begin
  Dic.Free;
end;

procedure TfrmMain.TCPServerConnect(AContext: TIdContext);
var
  Hostname: string;
begin
  Hostname := UpperCase(GStack.HostByAddress(AContext.Binding.PeerIP));
  if not Dic.ContainsKey(Hostname) then Dic.Add(Hostname, AContext);
end;

procedure TfrmMain.TCPServerDisconnect(AContext: TIdContext);
var
  Hostname: string;
begin
  Hostname := UpperCase(GStack.HostByAddress(AContext.Binding.PeerIP));
  if Dic.ContainsKey(Hostname) then
  begin
    Dic[Hostname].Free;
    Dic.Remove(Hostname);
  end;
end;

此代码线程是否安全?

2 个答案:

答案 0 :(得分:7)

总之:

如果您检查 TDictionary 的来源,您应该很快意识到实现本身没有提供线程安全性。即使它是,通过对 Dic 实例进行离散调用,您也有潜在的竞争条件:

  if Dic.ContainsKey(Hostname) then
  begin

    // In theory the Hostname key may be removed by another thread before you 
    //  get a chance to do this : ...

    Dic[Hostname].Free;
    Dic.Remove(Hostname);
  end;

你需要自己使用 Dic 线程安全,幸运的是在这个例子中,使用对象本身的监视器很容易实现:

MonitorEnter(Dic);
try
  if not Dic.ContainsKey(Hostname) then 
    Dic.Add(Hostname, AContext);

finally
  MonitorExit(Dic);
end;


// ....


MonitorEnter(Dic);
try
  if Dic.ContainsKey(Hostname) then
  begin
    Dic[Hostname].Free;
    Dic.Remove(Hostname);
  end;

finally
  MonitorExit(Dic);
end;

如果您不熟悉Delphi中的监视器,简单来说,您可以将监视器视为每个 TObject 后代支持的随时可用的关键部分(在旧版Delphi中)不支持这些显示器,你可以用明确的关键部分实现同样的目标。)

答案 1 :(得分:3)

要回答您的具体问题 - 不,TDictionary不是线程安全的,因此您必须保护对它的访问权。

您的代码未处理连接到同一服务器的代理/路由器后面的多个客户端的可能性。它们都具有相同的PeerIPHostName值。这些值本身并不足以识别客户。您需要创建自己的唯一标识符,例如让您的客户端使用用户名登录服务器,然后将其用作字典密钥。

最后,不要释放TIdContext个对象!它们归TIdTCPServer所有,并在OnDisconnect事件处理程序退出后自动释放。