2012-06-20 41 views
2

我正在通過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

+0

FBuffer是一個常規的'string'嗎? –

+0

@WarrenP是的,只是一個普通的字符串。 –

+3

將數據加載到某個Tbuffer對象並將其排隊到生產者 - 消費者隊列上的處理線程更爲常見。只要您關閉一個Tbuffer實例,請創建一個新實例。這完全避免了與共享字符串相關的大量繁瑣,雜亂的代碼等,並且消除了對新數據的極度浪費的輪詢。這一點,並消除虛假分享。 –

回答

5

是的,您必須保護字符串緩衝區免受併發訪問。印地有一個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; 
+0

'ReadLn('#');'這將擦除客戶端輸入緩衝區中的數字,假設一個數據包有成千上萬個字節,如果所有的數據都不可用,那麼這個數字在下次這個被執行。有沒有一種方法可以讀取而不從緩衝區中刪除它?否則,我將不得不在這個線程中設置某種讀取狀態屬性,也存儲這個數字,並在下一次線程執行時,如果檢查這個讀取狀態,等等...... –

+1

@JerryDodge Indy讀取操作阻塞,直到所有數據(例如達到終止符或期望的字節數),所以你的代碼將不必處理'不可用'的數據(超時除外)。 – mjn

+0

準確。 'ReadLn()'會在退出前等待'#''字符到達。 'ReadString()'將等待指定的字節數到達之前退出。唯一會影響的是如果你已經分配了一個'ReadTimeout'值。如果'ReadLn()'超時,它將返回一個空白字符串,將所有當前數據留在InputBuffer中,並且'ReadString()'不會被調用,因爲'StrToIntDef()'將返回0.如果'ReadString )超時,它會引發一個'EIdReadTimeout'異常,在這種情況下,你應該重置你的連接。 –

5

是的,您必須保護從多個線程訪問時的字符串,您可以使用crtical部分。看看EnterCriticalSection,LeaveCriticalSection,InitializeCriticalSectionDeleteCriticalSection的功能。

+0

所以大概我可以圍繞這個字符串構建一個小類,例如一個過程'AddToBuffer(const S:String);'我在'try..finally'塊中執行'EnterCriticalSection()'和'LeaveCriticalSection'和讀取數據一樣),對嗎? –

+0

您不需要創建一個新程序,您可以直接在代碼中包含對「EnterCriticalSection」和「LeaveCriticalSection」方法的調用。 – RRUZ

+3

Indy已經有這樣一個類 - TIdThreadSafeString。 –