You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Delphi中多线程传递字符串后修改是否安全?

Delphi多线程中修改线程自有字符串变量是否安全?

我知道Delphi中的字符串采用引用计数机制,引用计数本身是原子操作,但字符串变量本身不是。也清楚在不同线程中读写同一个字符串变量时必须使用锁。但疑问是:在线程构造阶段将一个字符串传递给多个线程,启动线程后,线程内部修改自己持有的这个字符串是否安全?

代码示例

type
  TThreadTest = class(TThread)
    public
      FStr: string;
    protected
      procedure Execute; override;
  end;

{ TThreadTest }

procedure TThreadTest.Execute;
begin
  inherited;
  var i: Int64 := 0;
  while not Terminated do
  begin
    Inc(i);
    // 线程内部修改自己的FStr
    FStr := i.ToString;
    Sleep(1);
  end;
end;

// 全局字符串变量
var G_Str: string := 'test1';

// 主线程中给线程1复制字符串引用
var Thread1 := TThreadTest.Create(True);
Thread1.FStr := G_Str;

// 主线程中给线程2复制字符串引用
var Thread2 := TThreadTest.Create(True);
Thread2.FStr := G_Str;

// 清除全局变量的引用,此时Thread1和Thread2的FStr指向同一个字符串实例
G_Str := 'other';

// 启动两个线程
Thread1.Start;
Thread2.Start;

调试观察到的字符串赋值逻辑

在x64调试器中,Thread1.FStr := G_Str;会调用System.pas中的_UStrAsg函数,该函数负责字符串的引用计数更新和指针赋值:

procedure _UStrAsg(var Dest: UnicodeString; const Source: UnicodeString);
var
  S, D: Pointer;
  P: PStrRec;
  Len: Integer;
begin
  S := Pointer(Source);
  if S <> nil then
  begin
    if __StringRefCnt(Source) < 0 then
    begin
      // 处理不可共享的字符串(比如常量字符串),复制新实例
      Len := __StringLength(Source);
      S := _NewUnicodeString(Len);
      Move(Pointer(Source)^, S^, Len * SizeOf(WideChar));
    end else
    begin
      // 对可共享字符串,原子增加引用计数
      P := PStrRec(PByte(S) - SizeOf(StrRec));
      AtomicIncrement(P.refCnt);
    end;
  end;
  D := Pointer(Dest);
  // 将源字符串的指针赋值给目标变量
  Pointer(Dest) := S;
  if D <> nil then
  begin
    // 原子减少原目标字符串的引用计数,为0则释放内存
    P := Pointer(PByte(D) - SizeOf(StrRec));
    if (P.refCnt > 0) and (AtomicDecrement(P.refCnt) = 0) then
      FreeMem(P);
  end;
end;

结论:线程内部修改自有字符串变量是安全的

核心原因如下:

  • 线程构造阶段的字符串赋值操作都在主线程中执行,_UStrAsg的引用计数原子操作保证了这一步的安全性,最终Thread1和Thread2的FStr指向同一个字符串实例,引用计数为2。
  • 线程启动后,每个线程操作的是自己的FStr成员变量:这是每个线程独有的变量,不存在多个线程同时读写同一个变量的情况。
  • 当线程执行FStr := i.ToString时,本质是对自己的FStr变量进行赋值:
    1. 先创建新的字符串实例(i.ToString),初始引用计数为1;
    2. 调用_UStrAsg将新字符串的指针赋值给自己的FStr,同时原子递减原有字符串的引用计数(此时原有字符串的引用计数从2变为1,不会被释放)。
  • 整个过程中,没有多个线程竞争同一个字符串变量,也不会修改共享的字符串实例(Delphi字符串是不可变的,赋值会生成新实例),因此不需要加锁,操作是安全的。

内容的提问来源于stack exchange,提问作者qgi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 16:35:16