从两个线程保护字符串缓冲区?

时间:2012-06-20 21:43:19

标签: multithreading string delphi buffer indy

我正在通过Indy套接字处理流数据包字符串,在客户端,我有一个线程从TIdTCPClient读取传入数据,并不断将此数据附加到单个字符串缓冲区的末尾。我有另一个线程,它从头开始连续读取此缓冲区,根据需要复制(和删除)数据(一次一个完整的数据包)。

我知道在任何情况下,访问同一个变量的两个线程都是危险的。但这也适用于字符串吗?或只是对象?从两个不同的线程读取/写入相同的字符串,我能感觉安全吗?如果没有,那么我该怎么做才能保护这个字符串呢?这是一个名为FBuffer的纯字符串。

我将数据追加到最后:

procedure TListenThread.CheckForData;
begin
  if FClientSocket.Connected then begin
    FClientSocket.IOHandler.CheckForDataOnSource(5000);
    if not FClientSocket.IOHandler.InputBufferIsEmpty then
      FBuffer:= FBuffer + FClientSocket.IOHandler.InputBufferAsString;
  end;
end;

另一个主题是这样读它:

procedeure TPacketThread.CheckForPacket;
var
  P: Integer; //Deliminator position
  T: String;  //Temp copying string
  Z: Integer; //Expected packet size
begin
  P:= Pos('#', FBuffer);
  if P > 0 then begin //Is the deliminator found?
    T:= Copy(FBuffer, 1, P-1); //Copy up to deliminator...
    Z:= StrToIntDef(T, 0); //Convert packet size to integer...
    if Z > 0 then begin
      //Is there a full packet waiting in buffer?
      if Length(FBuffer) >= Z then begin
        //First, delete size definition and deliminator...
        Delete(FBuffer, 1, P);
        //Now grab the rest of it up to the packet size...
        T:= Copy(FBuffer, 1, Z);
        //Delete what we just copied...
        Delete(FBuffer, 1, Z);
        //Finally, pass this packet string for further processing...
        ProcessPacket(T);
      end;
    end;
  end;
end;

代码是我的代码的简化版本,只是为了演示我需要对FBuffer执行的所有工作。

2 个答案:

答案 0 :(得分:5)

是的,从多个线程访问时必须保护字符串,您可以使用关键部分来执行此操作。查看EnterCriticalSectionLeaveCriticalSectionInitializeCriticalSectionDeleteCriticalSection函数。

答案 1 :(得分:5)

是的,您必须保护字符串缓冲区不受并发访问的影响。 Indy有一个TIdThreadSafeString类可以用于此目的,例如:

FBuffer: TIdThreadSafeString;
// make sure to Create() and Free() as needed..

procedure TListenThread.CheckForData; 
begin 
  if FClientSocket.Connected then begin 
    FClientSocket.IOHandler.CheckForDataOnSource(5000); 
    if not FClientSocket.IOHandler.InputBufferIsEmpty then 
      FBuffer.Append(FClientSocket.IOHandler.InputBufferAsString);
  end; 
end; 

procedure TPacketThread.CheckForPacket; 
var 
  P: Integer; //Deliminator position 
  T: String;  //Temp copying string 
  Z: Integer; //Expected packet size 
begin 
  FBuffer.Lock;
  try
    P:= Pos('#', FBuffer.Value); 
    if P > 0 then begin //Is the deliminator found? 
      T := Copy(FBuffer.Value, 1, P-1); //Copy up to deliminator... 
      Z := StrToIntDef(T, 0); //Convert packet size to integer... 
      if Z > 0 then begin 
        //Is there a full packet waiting in buffer? 
        if Length(FBuffer.Value) >= Z then begin 
          //First, delete size definition and deliminator... 
          FBuffer.Value := Copy(FBuffer.Value, P+1, MaxInt); 
          //Now grab the rest of it up to the packet size... 
          T := Copy(FBuffer.Value, 1, Z); 
          //Delete what we just copied... 
          FBuffer.Value := Copy(FBuffer.Value, Z+1, MaxInt); 
          //Finally, pass this packet string for further processing... 
          ProcessPacket(T); 
        end; 
      end; 
    end; 
  finally
    FBuffer.Unlock;
  end;
end; 

话虽如此,鉴于你所展示的数据包格式化,我会采取不同的策略:

FBuffer: TIdThreadSafeStringList;
// make sure to Create() and Free() as needed..

procedure TListenThread.CheckForData; 
var 
  T: String;  //Temp copying string 
  Z: Integer; //Expected packet size 
begin 
  if FClientSocket.Connected then begin 
    if FClientSocket.IOHandler.InputBufferIsEmpty then begin
      FClientSocket.IOHandler.CheckForDataOnSource(5000);
      if FClientSocket.IOHandler.InputBufferIsEmpty then Exit;
    end; 
    // data is available, keep reading as long as packets are present...
    repeat
      T := FClientSocket.IOHandler.ReadLn('#');
      Z := StrToIntDef(T, 0);
      if Z > 0 then begin 
        T := FClientSocket.IOHandler.ReadString(Z); 
        FBuffer.Add(T); 
      end; 
    until FClientSocket.IOHandler.InputBufferIsEmpty;
  end; 
end; 

procedure TPacketThread.CheckForPacket; 
var 
  L: TStringList;
  T: String;
begin 
  L := FBuffer.Lock;
  try
    if L.Count = 0 then Exit;
    T := L[0];
    L.Delete(0);
  finally
    FBuffer.Unlock;
  end;
  ProcessPacket(T); 
end;