首页 > 解决方案 > 多线程访问和编辑同一个双精度数组

问题描述

我需要遍历数组中的每个双精度来执行“拉普拉斯平滑”,“混合值”与相邻双精度。

我会将存储的值保存在临时克隆数组中,最后更新原始值。

伪代码:

double[] A = new double[1000];
// Filling A with values...

double[] B = A.Clone as double[];

for(int loops=0;loops<10;loops++){ // start of the loop

    for(int i=0;i<1000;i++){ // iterating through all doubles in the array
    // Parallel.For(0, 1000, (i) => {

       double v= A[i];
       B[i]-=v;
       B[i+1]+=v/2;
       B[i-1]+=v/2;
       // here i'm going out of array bounds, i know. Pseudo code, not relevant.

    }
    // });
}
A = B.Clone as double[];

使用for它可以正常工作。“平滑”数组中的值。

Parallel.For()遇到了一些访问同步问题:线程正在发生冲突,并且某些值实际上没有正确存储。线程多次访问和编辑同一索引处的数组。

(我没有在线性数组中测试过这个,我实际上正在研究一个多维数组[x,y,z] ..)

我该如何解决这个问题?

我正在考虑为每个线程创建一个单独的数组,然后再进行求和......但我需要知道线程索引并且我在网络上的任何地方都没有找到。(即使使用完全不同的解决方案,如果存在“线程索引”,我仍然感兴趣......)。

我会接受任何解决方案。

标签: c#arraysparallel-processingarrayaccess

解决方案


您可能需要该方法的更高级重载之一Parallel.For

public static ParallelLoopResult For<TLocal>(int fromInclusive, int toExclusive,
    ParallelOptions parallelOptions, Func<TLocal> localInit,
    Func<int, ParallelLoopState, TLocal, TLocal> body,
    Action<TLocal> localFinally);

使用线程本地数据执行 for 循环,其中迭代可以并行运行,可以配置循环选项,并且可以监视和操作循环的状态。

对于它所期望的所有各种 lambda,这看起来非常令人生畏。这个想法是让每个线程处理本地数据,最后合并数据。以下是如何使用此方法来解决您的问题:

double[] A = new double[1000];
double[] B = (double[])A.Clone();
object locker = new object();
var parallelOptions = new ParallelOptions()
{
    MaxDegreeOfParallelism = Environment.ProcessorCount
};
Parallel.For(0, A.Length, parallelOptions,
    localInit: () => new double[A.Length], // create temp array per thread
    body: (i, state, temp) =>
    {
        double v = A[i];
        temp[i] -= v;
        temp[i + 1] += v / 2;
        temp[i - 1] += v / 2;
        return temp; // return a reference to the same temp array
    }, localFinally: (localB) =>
    {
        // Can be called in parallel with other threads, so we need to lock
        lock (locker)
        {
            for (int i = 0; i < localB.Length; i++)
            {
                B[i] += localB[i];
            }
        }
    });

我应该提一下,上述示例的工作负载过于细化,因此我不希望并行化能够大幅提高性能。希望您的实际工作量更大。例如,如果您有两个嵌套循环,则仅并行化外循环将非常有效,因为内循环将提供急需的块状。


替代解决方案:您可以直接更新 B 数组,而不是为每个线程创建辅助数组,并且仅在处理分区边界附近的危险区域中的索引时才使用锁:

Parallel.ForEach(Partitioner.Create(0, A.Length), parallelOptions, range =>
{
    bool lockTaken = false;
    try
    {
        for (int i = range.Item1; i < range.Item2; i++)
        {
            bool shouldLock = i < range.Item1 + 1 || i >= range.Item2 - 1;
            if (shouldLock) Monitor.Enter(locker, ref lockTaken);
            double v = A[i];
            B[i] -= v;
            B[i + 1] += v / 2;
            B[i - 1] += v / 2;
            if (shouldLock) { Monitor.Exit(locker); lockTaken = false; }
        }
    }
    finally
    {
        if (lockTaken) Monitor.Exit(locker);
    }
});

推荐阅读