Хочу разделить игру на 4 потока (пока что) . Пока что это чисто тестовые потоки, которые ничего не делают, хочу хоть как-то заставить их работать.
Идея такова: 1)инициализирую эти потоки перед вхождением в главный игровой цикл. 2)эти потоки внутри крутят бесконечный цикл, но вначале каждой итерации ждут оповещения от главного потока, чтобы он разрешил им работать 3)внутри главного игрового цикла каждую итерацию я оповещаю эти потоки о том, что они могут выполнить очередную итерацию по формированию кадра. 4)Тогда эти потоки снимаются с паузы и выполняют итерацию, после чего они должны оповестить главный поток о том, что они завершили работу и снова находятся на паузе. 5)главный поток ждёт оповещения ото всех запущенных потоков 6)следующая итерация главного игрового цикла.
Проблема: где-то получаю дедлок на рандомной итерации главного игрового цикла и в упор не понимаю, почему. Это мой первый опыт с многопоточкой, буду рад, если скажете, что у меня в коде не так и почему, потому что я ещё не совсем влился в тему.
Код делает дедлок даже если запустить 1 поток вместо 4-х, поэтому я 3 других даже закомментил, чтобы не мешались.
Главной функцией потока является ф-ция action() Она принимает аргументом ту функцию, которую должен вызывать поток собственно для работы. Внутри она крутит бесконечный цикл и ждёт разрешения на работу. Когда разрешение получено, она выполняет нужную функцию (например, render()), которая, в свою очередь, по завершении, даёт главному потоку информацию о том, что работа завершилась
Ф-ция update() вызывается из главного цикла и оповещает потоки о том, что они могут начинать работу, после чего ждёт завершения их выполнения.
Добавлено (11 ноября 2017, 17:49) --------------------------------------------- Кажись, решил проблему небольшим костылём Как было устроено: все потоки в начале фрейма ждут разрешение на работу, главный поток им его даёт. Потом он ждёт когда потоки оповестят его о завершении работы. При этом, для того чтобы обновить инфу о прогрессе работы потоков, использовался тот же мьютекс, который использовался в условной переменной, которая ждала результат. Возможно, потому я и ловил дедлок как решил: убрал условную переменную, которая ждала пока потоки не завершат кадр и не лочу в главном потоке соответствующий мьютекс. Вместо этого заставляю главный поток в цикле проверять, закончена ли работа и ждать. Так что, теперь итерация главного цикла (ф-ция update) выглядит так
Код
finishedFrameNum = 0;
for (int i = 0; i < THREADS_AMOUNT; i++) { std::unique_lock<std::mutex> iterationLock(mutIterationStarted[i]); threadReadyMap[i] = true; condIterationStarted[i].notify_one(); }
while (finishedFrameNum != THREADS_AMOUNT) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); }
Добавлено (11 ноября 2017, 18:02) --------------------------------------------- попробую теперь на этапе инициализации проверять, сколько ядер в компе юзера. Если 4+ - запускать 3-4 потока Если 2 - запускать 2 потока, в первом - рендер+что-то не слишком тяжёлое, во втором - всё остальное Если 1 - 2 потока, рендер в одном, всё остальное во втором
Добавлено (11 ноября 2017, 22:15) --------------------------------------------- Всё, обернул всё это дело в класс. Можно создать произвольное количество потоков, в каждый из потоков запихнуть произвольное количество последовательно исполняемых задач.
Только одна проблема есть: сделал синглтон, отнаследовался от него и попытался получать инстанс нужного класса, не получалось - психанул и удалил наследование от синглтона, всё равно я один над проектом сижу, и так знаю, что класс в единственном экземпляре должен быть))
Код
#pragma once #include <vector> #include <mutex> #include <thread> #include <condition_variable>
for (int i = 0; i < threadsNum_; i++) { std::unique_lock<std::mutex> iterationLock(*mutIterationStarted_[i]); threadReadyMap_[i] = true; condIterationStarted_[i]->notify_one(); }
while (finishedFrameNum_ != threadsNum_) { std::this_thread::sleep_for(std::chrono::microseconds(waitIntervalMicroseconds_)); }
postUpdateFunction_(deltaTime_); }
void Threading::action(int threadId) { while (true) { std::unique_lock<std::mutex> iterationLock(*mutIterationStarted_[threadId]); bool exit = false; while (true) { //wait for starting new game iteration condIterationStarted_[threadId]->wait(iterationLock);
if (!running) { exit = true; break; } else if (threadReadyMap_[threadId]) { //to avoid spurious wakeup threadReadyMap_[threadId] = false; break; } } if (exit) break;
//call all functions of the thread for (int i = 0; i < functions_[threadId].size(); i++) { functions_[threadId][i](deltaTime_); }
//increase counter of threads that finished making this frame notifyFinishFrame(); } }
void postUpdate(float dt) { static int a; a++; if (a > 1000) { threading.quit(); } }
void render(float dt) { int k = 0; for (int i = 0; i < 1000000; i++) { k++; } }
void physics(float dt) { int k = 0; for (int i = 0; i < 1000000; i++) { k++; } }
void AI(float dt) { int k = 0; for (int i = 0; i < 1000000; i++) { k++; } }
void misc(float dt) { int k = 0; for (int i = 0; i < 1000000; i++) { k++; } }
Добавлено (12 ноября 2017, 12:09) --------------------------------------------- упс, ещё один дедлок был, который случался довольно редко (тобишь прога стабильно работала несколько десятков секунд в полной нагрузке, прежде чем валилась) На этот раз связанный со "spurious wakeup"
Дело в том, что может так совпасть, что поток сорвётся по ложному прерыванию за павру мгновений до того, как главный поток оповестит его о начале работы. Таким макаром, когда главный поток его оповещает, тот всё ещё занят обработкой ложного прерывания и усыплением себя, а ведь оповещение из главного потока прошло мимо! А потом мы ждём завершения работы потока, в то время как он пропустил оповещение о начале работы. Вылечил так: в цикле спамим оповещения, разблокируя при этом мьютекс условной переменной до тех пор, пока не убедимся, что наше сообщение дошло
Код
for (int i = 0; i < threadsNum_; i++) { std::unique_lock<std::mutex> iterationLock(*mutIterationStarted_[i]); threadReadyMap_[i] = true; while (threadReadyMap_[i]) { condIterationStarted_[i]->notify_one();
if (iterationLock.owns_lock()) { iterationLock.unlock(); } } }