Код содержит данные гонки при обновлении matches
. Если два потока делают это одновременно, оба могут прочитать одно и то же значение (скажем, 10), затем оба увеличивают его (до 11) и записывают новое значение обратно. В результате будет меньше зарегистрированных совпадений (в моем примере 11 вместо 12). Решение состоит в том, чтобы использовать System.Threading.Interlocked
для этой переменной.
Другие проблемы, которые я вижу:
- ваш последовательный цикл включает в себя итерацию для j
, равную trails
, а для параллельного цикла - нет (конечный индекс является исключительным в Parallel.For
);
- class Random
может быть не безопасным для потоков.
Обновление: я думаю, что вы не получите желаемый результат с кодом Дрю Марша, потому что он не обеспечивает достаточной рандомизации. Каждый из 1M экспериментов начинается с одного и того же случайного числа, потому что вы запускаете все локальные экземпляры Random с начальным числом по умолчанию. По сути, вы повторяете один и тот же эксперимент 1M раз, поэтому результат все еще искажен. Чтобы это исправить, вам нужно каждый раз вводить каждый случайный случай с новым значением. Обновление: я был не совсем прав, так как при инициализации по умолчанию для начального числа используются системные часы; однако MSDN предупреждает, что
поскольку часы имеют конечное разрешение, использование конструктора без параметров для создания различных случайных объектов в тесной последовательности создает генераторы случайных чисел, которые создают идентичные последовательности случайных чисел.
Так что это все еще может быть причиной недостаточной рандомизации, и с явными начальными значениями вы можете получить лучшие результаты. Например, инициализация с номером итерации внешнего цикла дала мне хороший ответ:
Parallel.For(0, trails + 1, j =>
{
Random rnd = new Random(j); // initialized with different seed each time
/* ... */
});
Однако я заметил, что после того, как инициализация Random
была перенесена в цикл, все ускорение было потеряно (на моем ноутбуке Intel Core i5). Поскольку я не эксперт по C #, я не знаю почему; но я предполагаю, что класс Random
может иметь некоторые данные, совместно используемые всеми экземплярами с синхронизацией доступа.
Обновление 2: с использованием ThreadLocal
для хранения одного экземпляра Random
на поток, я получил хорошую точность и разумное ускорение:
ThreadLocal<Random> ThreadRnd = new ThreadLocal<Random>(() =>
{
return new Random(Thread.CurrentThread.GetHashCode());
});
Parallel.For(0, trails + 1, j =>
{
Random rnd = ThreadRnd.Value;
/* ... */
});
Обратите внимание, как рандомизаторы для каждого потока инициализируются хэш-кодом для текущего запущенного экземпляра Thread
.