【问题标题】:Can I (safely) write to an Array in a Parallel.For loop construct?我可以(安全地)在 Parallel.For 循环构造中写入数组吗?
【发布时间】:2015-10-26 13:43:08
【问题描述】:

对于迭代范围的所有索引,下面的代码 sn-p 是否会正确写入results

虽然有多个线程同时访问同一个对象;由于索引,每个线程都在写入内存中的唯一位置。

let results:NewType[] = Array.zeroCreate temp.Length
let mutable data = Unchecked.defaultof<OldType>

let loopResult = 
    System.Threading.Tasks.Parallel.For(
        0, 
        temp.Length, 
        (fun i -> 
            data <- temp.[i]
            results.[i] <- NewType(data.X, data.Y) 
        )
    )

【问题讨论】:

  • 正如已经指出的那样,这不是线程安全的,但也不是高性能的。因为不同的内核写入和读取相同的内存区域(数据),所以内核之间可能会有很多“聊天”以确保缓存的一致性(取决于架构)。为了获得良好的并行性能,您需要确保每个内核可以相互独立地执行(即不共享可变数据)。一个提示是始终将单核性能与多核性能进行比较。令人惊讶的是,人们经常看到减速而不是加速。最容易通过更改进程亲和性来完成

标签: .net multithreading parallel-processing f#


【解决方案1】:

按照您编写此代码的方式,它在并行迭代中将无法正常运行。您添加了一个可变临时对象,这将破坏您安全地并行化此代码的能力。写入 results 是安全的,因为您知道每个线程将访问数组的不同元素,但写入 data 是不安全的,因为许多线程可以分配给该可变对象同时。

如果您这样重构代码:

let results:NewType[] = Array.zeroCreate temp.Length

let loopResult = 
    System.Threading.Tasks.Parallel.For(
        0, 
        temp.Length, 
        (fun i -> 
            let data = temp.[i]
            results.[i] <- NewType(data.X, data.Y) 
        )
    )

这种行为将是安全的。

然而,这段代码只是简单的并行映射,所以这样写会更习惯:

let loopResult = temp |> Array.Parallel.map (fun data -> NewType(data.X, data.Y))

【讨论】:

  • 谢谢。我应该期待Array.Parallel.mapSystem.Threading.Tasks.Parallel.For 之间的性能差异吗?
  • 我不能说我已经进行了广泛的分析,但是在 FSI 中进行的快速测试表明它们的运行速度大致相同,这正是我直觉所期望的。使用 System.Threading.Tasks.Parallel.For 实现一个等效于 Array.Parallel.map 的函数很简单,所以我看不出性能差异的原因。
【解决方案2】:

由于 F# 数组的类型通常是 .NET System.Array,因此同时写入数组的不同部分将是线程安全的。这已经讨论过HEREHERE

【讨论】:

    猜你喜欢
    • 2011-04-14
    • 1970-01-01
    • 2016-09-07
    • 1970-01-01
    • 2020-05-02
    • 2015-03-26
    • 1970-01-01
    • 2019-08-07
    • 2017-03-21
    相关资源
    最近更新 更多