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

如何用Parallel.ForEach并行遍历多个Span?

C#并行遍历Span的实现方案

任务要求

把同步遍历代码改成Parallel.ForEach并行实现,全程用Span处理,而且这是热点代码(数十亿次迭代),绝对不能有内存分配。

原同步代码

for (int i4 = 0; i4 < ba_result_out.Length; i4 += c.i_key_size)
{
  long k = BitConverter.ToInt64(spn_data.Slice(i4, c.i_key_size));
  if (Dictionary.md1.ContainsKey(k)) { 
    // 省略部分业务逻辑
  }
}

尝试的并行代码(报错)

ParallelOptions po = new ParallelOptions { MaxDegreeOfParallelism = 2 };
var rp = Partitioner.Create(0, ba_result_out.Length / c.i_key_size, po.MaxDegreeOfParallelism);
Parallel.ForEach(rp, po, (range, loopState) =>
{
  // 编译器直接报错:Lambda里不能用Span
  for (int i4 = range.Item1; i4 < range.Item2; i++)
  {
    long k = BitConverter.ToInt64(spn_data.Slice(i4, c.i_key_size));
    if(Dictionary.ContainsKey(k)){
      // 省略部分业务逻辑
    }
  }
});

问题&疑问

  • 问题:编译器不让在Lambda里捕获Span,因为Span是栈分配类型,Lambda会被包装成闭包,容易触发生命周期问题
  • 疑问:能不能并行遍历多个独立的Span?

可行方案

当然可以,核心是别让Lambda捕获外部Span,而是传递内存的起始/结束索引,在Lambda内部重新构造子Span,同时保证线程安全:

最终代码

ReadOnlySpan<byte> sourceSpan = spn_data;
int keySize = c.i_key_size;
// 按键的数量分区,避免拆分到键的中间
var partitioner = Partitioner.Create(0, sourceSpan.Length / keySize);

Parallel.ForEach(partitioner, new ParallelOptions { MaxDegreeOfParallelism = 2 }, range =>
{
  // 计算当前分区对应的字节范围
  int startBytePos = range.Item1 * keySize;
  int endBytePos = range.Item2 * keySize;
  
  // 在Lambda内部构造当前线程要处理的子Span,完全不依赖外部闭包
  ReadOnlySpan<byte> currentPartitionSpan = sourceSpan.Slice(startBytePos, endBytePos - startBytePos);
  
  // 遍历当前子Span里的每个键
  for (int i = 0; i < currentPartitionSpan.Length; i += keySize)
  {
    long k = BitConverter.ToInt64(currentPartitionSpan.Slice(i, keySize));
    // 注意:如果字典有写入操作,必须用ConcurrentDictionary或者加锁;只读的ContainsKey没问题
    if (Dictionary.md1.ContainsKey(k))
    {
      // 省略部分业务逻辑
    }
  }
});

关键注意点

  1. 避开闭包捕获Span:Lambda里直接用外部Span会触发编译错误,因为Span不能被闭包持有。通过传递索引在内部构造Span,彻底解决这个问题。
  2. 零内存分配:所有Span操作都是栈上的,没有堆内存分配,完全符合热点代码的性能要求。
  3. 线程安全:如果业务逻辑里要修改字典,必须用线程安全的ConcurrentDictionary或者加锁;如果只是执行只读的ContainsKey操作,只要并行过程中字典不被其他线程修改,就不用额外处理。
  4. 分区合理性:按键的数量来分区,而不是字节数,避免把一个完整的键拆到两个分区里,逻辑更顺畅。

补充说明

Span本身支持并行访问,只要每个线程处理的是Span的不同区域,就不会有冲突。上面的方案就是把原Span拆成多个不重叠的子Span,分给不同线程并行处理,完全满足需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 06:05:21