Функция, работающая с парой итераторов, не работает при параллельном запуске
У меня есть следующая шаблонная функция, которая принимает GameName (std::string) и пару итераторов начала / конца над коллекцией GameTime (size_t). Он перебирает диапазон и добавляет GameTime-ы вместе и возвращает кортеж из названия игры, общего игрового времени и среднего игрового времени (GameStats):
template<typename InputIt>
GameStats calculateGameStats(const GameName& _gameName, const InputIt _begin, const InputIt _end)
{
std::string logMessage = "Started process for game " + _gameName + ":[ ";
for_each(_begin,_end,[&logMessage](GameTime e){ logMessage += std::to_string(e) + ',';});
std::clog << logMessage + " ]\n";
size_t itemCount = 0;
GameTime gameTime =
std::accumulate(_begin,_end,0,[&itemCount](const GameTime _lhs, const GameTime _rhs)
{
++itemCount;
return _lhs + _rhs;
});
logMessage = "Ended process for game " + _gameName + ":[ ";
for_each(_begin,_end,[&logMessage](GameTime e){ logMessage += std::to_string(e) + ',';});
std::clog << logMessage + " ]\n";
return std::make_tuple(_gameName, gameTime, gameTime / itemCount);
}
(Для целей отладки я также перечисляю элементы, которые мы повторяли в начале и в конце. Очевидно, они должны быть одинаковыми каждый раз, однако см. Ниже.)
Аналогичная версия этой функции, использующая ссылку на std::vector, работала нормально, однако эта новая, похоже, испортила свои итераторы, когда несколько ее экземпляров выполняются параллельно. Вот код, который запускает процесс для каждой игры:
// Start processing each game's stats in parallel
std::vector< std::future<GameStats> > processVector;
processVector.reserve(gameCount);
for(const std::pair<GameName, std::vector<GameTime> >& entryListPair : gameEntries)
{
const std::string& gameName = entryListPair.first;
const std::vector<GameTime>& entryList = entryListPair.second;
processVector.push_back(std::async(std::launch::async,
&calculateGameStats<decltype(entryList.cbegin())>,
gameName,
entryList.cbegin(),
entryList.cend()));
assert((processVector.cend()-1)->valid());
}
(GameEntries - это отображение типа std::map GameName на вектор GameTime)
Вот соответствующая часть вывода от запуска программы:
Started process for game CoD:[ 182,1264, ]
Ended process for game CoD:[ 606,1667, ]
Started process for game DotA:[ 606,1667, ]
Ended process for game DotA:[ 606,1667, ]
Started process for game GTAV:[ 606, ]
Ended process for game GTAV:[ 606, ]
Started process for game HotS:[ 606, ]
Ended process for game HotS:[ 606, ]
Started process for game LoL:[ 1277,193, ]
Ended process for game LoL:[ 1277,193, ]
Started process for game MC:[ 857,193, ]
Ended process for game MC:[ 857,193, ]
Started process for game OW:[ 0, ]
Note: 7 games in map, created 7 processes.
Ended process for game OW:[ 140377361861512, ]
Writing entry: CoD 2273 1136
Writing entry: DotA 2273 1136
Writing entry: GTAV 606 606
Writing entry: HotS 606 606
Writing entry: LoL 1470 735
Writing entry: MC 1050 525
Writing entry: OW 650759048 650759048
After processing: CoD:[ 1354,1442,]
After processing: DotA:[ 2137,1264,]
After processing: GTAV:[ 182,]
After processing: HotS:[ 2551,]
After processing: LoL:[ 606,1667,]
After processing: MC:[ 1277,193,]
After processing: OW:[ 857,]
Done!
Запуск программы более одного раза дает разные результаты, начиная от правильных результатов в некоторых играх и заканчивая ошибочными номерами везде. Я также перечисляю все записи GameTime для каждой игры после завершения программы, чтобы удостовериться, что она не была изменена, в случае, если проблема именно в этом, но все векторы выходят из нее невредимыми.
Однако, как видно из выходных данных, повторение от (предположительно постоянного и неизмененного) начала и до конца в одной и той же функции каждый раз дает разные результаты. Это только в том случае, если задачи выполняются параллельно. Если запустить последовательно (вызывая wait() для каждого будущего перед запуском следующего), программа запускается правильно, поэтому я предполагаю, что каждый поток по какой-то причине лишает законной силы итераторы других, даже если они являются входными итераторами для разных векторов, которые были все переданы по значению.
Я хотел бы знать, что вызывает это вмешательство и как я мог заставить их работать параллельно.
1 ответ
Замещать const std::pair<GameName, std::vector<GameTime> >&
с auto&&
или же auto const&
и позвони мне утром.
Ваш код копирует вектор в каждом цикле, потому что вы получили неверный тип пары, сохраненной на карте. const&
можно привязать к временному, а тип на карте можно преобразовать в используемый вами тип. когда const&
непосредственно связывается с временным, вы получаете продление срока действия ссылки, и временное действие длится до тех пор, пока ссылка не выйдет из области видимости.
Конверсия копирует оба GameName
и std::vector<GameTime>
,
const&
затем выходит из области видимости в конце цикла, увеличивая временное время его жизни. std::vector<GameTime>
уничтожен Обычно его память затем повторно используется на следующей итерации цикла.
Если вам интересно, ваш std::map<Blah>::value_type
является
std::pair<const GameName, std::vector<GameTime> >
в то время как ваш код выше не делает первый аргумент const
, Тем не менее, типы здесь на самом деле менее информативны, код будет молча подвержен ошибкам, если они не будут точно совпадать с типами на карте, и, как таковой, это идеальное место для использования. auto
,
Указывать только тип в for(:)
цикл, если вы действительно намереваетесь вызвать преобразование в этот тип. Если вы хотите перебрать копии, сделайте for(auto x:y)
, если над изменчивыми ссылками сделать for(auto& x:y)
, если вы хотите заявить, что вы не мутируете или делать for(auto const& x:y)
или (лучше, но C++17) for(auto&& x:std::as_const(y))
,
И если вам не все равно, а просто хотите быть эффективным, сделайте for(auto&& x:y)
,