Parallel.For和Parallel.ForEach没有得出结论

时间:2014-02-12 08:07:56

标签: c# task-parallel-library parallel.foreach

这是一个失败的测试。如何确认循环运行的次数是否正确?

    public Random Randomator { get; set; }
    public const int TimesToRun = 1000000;

    [TestMethod]
    public void ThrowTheDice()
    {
        Randomator = new Random();

        var resultsParallel = new Dictionary<int, int>
        {
            {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}
        };

        var resultsParallelForEach = new Dictionary<int, int>
        {
            {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}
        };

        var stopwatch = new Stopwatch();
        stopwatch.Start();
        Parallel.For(0, TimesToRun, ctr =>
        {
            var val = ThrowDice();
            if (!resultsParallel.ContainsKey(val))
                throw new ArgumentOutOfRangeException();

            var existing = resultsParallel[val];
            resultsParallel[val] = existing + 1;
        });

        stopwatch.Stop();
        var parallelTime = stopwatch.Elapsed;

        stopwatch = new Stopwatch();
        stopwatch.Start();
        var numbers = Enumerable.Range(0, TimesToRun);
        Parallel.ForEach(numbers, ctr =>
        {
            var val = ThrowDice();
            if (!resultsParallel.ContainsKey(val))
                throw new ArgumentOutOfRangeException();

            var existing = resultsParallelForEach[val];
            resultsParallelForEach[val] = existing + 1;
        });

        stopwatch.Stop();
        var parallelForEachTime = stopwatch.Elapsed;

        var parallelTotal = resultsParallel.Sum(x => x.Value);
        var parallelForEachTotal = resultsParallelForEach.Sum(x => x.Value);

        Assert.AreEqual(parallelTotal, TimesToRun);
        Assert.AreEqual(parallelForEachTotal, TimesToRun);
    }

    public int ThrowDice()
    {
        return Randomator.Next(1, 7);
    }

3 个答案:

答案 0 :(得分:7)

与此同时,你正在运行这些行:

var existing = resultsParallel[val];
resultsParallel[val] = existing + 1;

对于任何特定的val值,无法保证只有一个线程/任务同时运行这些行。因此,两个线程可以读取值2,添加1,并存储值3.您需要使用线程安全的方法来累积总数。

E.g。您可以使用Parallel.For的重载,允许每个线程单独构建自己的结果副本,然后进行最后的组合步骤,以便计算总结果:

public static ParallelLoopResult For<TLocal>(
    long fromInclusive,
    long toExclusive,
    Func<TLocal> localInit,
    Func<long, ParallelLoopState, TLocal, TLocal> body,
    Action<TLocal> localFinally
)

答案 1 :(得分:1)

您正在使用非线程安全的哈希表实现。因此,你只能证明你犯了一个错误。请改用ConcurrentDictionary,这是线程安全的:

var resultsParallel = new ConcurrentDictionary<int, int>();

var stopwatch = new Stopwatch();
stopwatch.Start();
Parallel.For(0, TimesToRun, ctr =>
{
    var val = ThrowDice();
    resultsParallel.AddOrUpdate(val, 1, (key, old) => old + 1);
});

答案 2 :(得分:0)

您可以使用信号量序列化对resultsParallel的并发访问,resultsParallelForEach:

公共类示例     {         public static Random Randomator {get;组; }         public const int TimesToRun = 1000000;

    public static Semaphore semaphore;


    public static void ThrowTheDice()
    {
        Randomator = new Random();

        semaphore = new Semaphore(1, 1);

        var resultsParallel = new Dictionary<int, int>
    {
        {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}
    };

        var resultsParallelForEach = new Dictionary<int, int>
    {
        {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}
    };

        var stopwatch = new Stopwatch();
        stopwatch.Start();
        Parallel.For(0, TimesToRun, ctr =>
        {
            var val = ThrowDice();
            if (!resultsParallel.ContainsKey(val))
                throw new ArgumentOutOfRangeException();

            semaphore.WaitOne();

            var existing = resultsParallel[val];
            resultsParallel[val] = existing + 1;

            semaphore.Release();
        });

        stopwatch.Stop();
        var parallelTime = stopwatch.Elapsed;

        stopwatch = new Stopwatch();
        stopwatch.Start();
        var numbers = Enumerable.Range(0, TimesToRun);
        Parallel.ForEach(numbers, ctr =>
        {
            var val = ThrowDice();
            if (!resultsParallel.ContainsKey(val))
                throw new ArgumentOutOfRangeException();

            semaphore.WaitOne();

            var existing = resultsParallelForEach[val];
            resultsParallelForEach[val] = existing + 1;

            semaphore.Release();
        });

        stopwatch.Stop();
        var parallelForEachTime = stopwatch.Elapsed;

        var parallelTotal = resultsParallel.Sum(x => x.Value);
        var parallelForEachTotal = resultsParallelForEach.Sum(x => x.Value);

        Debug.Assert(parallelTotal == TimesToRun);
        Debug.Assert(parallelForEachTotal == TimesToRun);
    }

    public static int ThrowDice()
    {
        return Randomator.Next(1, 7);
    }
}