Parallel.For и 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);
    }

person user10479    schedule 12.02.2014    source источник
comment
How can I confirm that the loops run the correct number of times? Они запускаются правильное количество раз, но вы получаете неконтролируемый доступ к общему объекту.   -  person L.B    schedule 12.02.2014


Ответы (3)


Параллельно вы запускаете следующие строки:

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

Нет никакой гарантии, что эти строки одновременно выполняются только одним потоком/задачей для любого конкретного значения val. Таким образом, два потока могут прочитать значение 2, добавить 1 и сохранить значение 3. Вам необходимо использовать потокобезопасные методы для накопления ваших итогов.

Например. вы можете использовать перегрузку Parallel.For который позволяет каждому потоку создавать свою собственную копию результатов отдельно, а затем имеет окончательный шаг объединения, чтобы вы могли вычислить общие результаты:

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

Вы используете реализации хеш-таблиц, которые не являются потокобезопасными. Таким образом, вы только доказываете, что допустили ошибку. Вместо этого используйте 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);
});
person Georg    schedule 12.02.2014

Вы можете использовать семафор для сериализации параллельного доступа к resultsParallel, resultsParallelForEach:

открытый пример класса { общественный статический Randomator { получить; установлен; } 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);
    }
} 
person Volker Wollmann    schedule 12.02.2014