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

多线程调用同一方法生成字符串时结果行数不符的问题咨询

多线程字符串拼接丢失数据的问题分析与解决

嘿,我来帮你捋清楚这个问题——你遇到的行数严重不足的情况,根源在于字符串的不可变性和多线程下的非原子操作,咱们一步步拆解:

为什么会丢数据?

首先,C#里的string是不可变类型,每次执行result += ...的时候,根本不是在原字符串上追加内容,而是创建了一个全新的字符串对象,再把result指向这个新对象。

而你写的result += A() + Environment.NewLine这个操作,本质上是三步:

  1. 读取当前result的旧值
  2. 用旧值拼接新内容,生成新字符串
  3. 把新字符串赋值回result

在多线程场景下,多个线程会同时执行这三步,就会出现「竞态条件」:比如线程A刚读完旧的result,还没完成拼接赋值,线程B也读到了同一个旧值,最后两个线程的拼接结果都会基于同一个旧值,导致其中一个线程的拼接结果直接被覆盖。这种情况发生成千上万次,最终结果自然只剩预期的十分之一。

而且你手动创建多个线程的方式,也没有做任何线程同步,完全是裸奔式的并发写入,数据丢失几乎是必然的。

解决方案(推荐几种实用方式)

方式1:用线程安全集合存内容,最后合并(性能最优)

先把每一行内容存入线程安全的集合,等所有线程跑完再一次性合并成字符串,避免频繁的并发写入冲突:

// 用ConcurrentBag存储每行,它是专门为多线程设计的安全集合
ConcurrentBag<string> lineCollection = new ConcurrentBag<string>();

private void Testing()
{
    var threads = new List<Thread>();
    // 这里按10万行分配任务,比如每个线程跑4次,25000个线程(也可以每个线程跑更多次减少线程数)
    for (int i = 0; i < 25000; i++)
    {
        threads.Add(new Thread(() =>
        {
            for (int j = 0; j < 4; j++)
            {
                lineCollection.Add(A());
            }
        }));
    }

    // 启动所有线程
    foreach (var thread in threads) thread.Start();
    // 等待所有线程执行完毕
    foreach (var thread in threads) thread.Join();

    // 最后把所有行合并成最终字符串
    string result = string.Join(Environment.NewLine, lineCollection);
}

方式2:StringBuilder加锁(简单直接,但性能稍差)

如果习惯用StringBuilder,一定要配合锁来保证线程安全——普通的StringBuilder本身不是线程安全的:

StringBuilder sb = new StringBuilder();
// 定义一个专用的锁对象
object lockObj = new object();

private void Testing()
{
    var threads = new List<Thread>();
    for (int i = 0; i < 100000; i++)
    {
        threads.Add(new Thread(() =>
        {
            // 每次写入前加锁,保证同一时间只有一个线程操作StringBuilder
            lock (lockObj)
            {
                sb.Append(A()).Append(Environment.NewLine);
            }
        }));
    }

    foreach (var thread in threads) thread.Start();
    foreach (var thread in threads) thread.Join();

    string result = sb.ToString();
}

方式3:用Parallel并行库(更现代的写法)

现在C#更推荐用Task并行库来处理并发,代码更简洁,还能自动管理线程池:

ConcurrentBag<string> lineCollection = new ConcurrentBag<string>();

private void Testing()
{
    // 直接并行执行10万次任务,每次生成一行
    Parallel.For(0, 100000, _ =>
    {
        lineCollection.Add(A());
    });

    string result = string.Join(Environment.NewLine, lineCollection);
}

这种写法省去了手动创建和管理线程的麻烦,效率也更高。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 08:02:47