Параллелизм высокого уровня в игровом цикле Android

Я пытаюсь синхронизировать пару потоков, не связанных с пользовательским интерфейсом, один поток для запуска игровой логики и один поток для рендеринга, чтобы выполнять задачи в логическом и эффективном порядке. Я наложил ограничение на то, что вся система работает в стационарном состоянии без выделения, поэтому все экранные объекты возвращаются и "перерабатываются", и поэтому два потока должны поддерживать своего рода двусторонний диалог, который возникает, когда я вызываю Метод swapBuffers().

В псевдокоде порядок событий в игровом потоке выглядит примерно так:

while(condition)
{
    processUserInput();
    runGameLogicCycle(set number of times);

    waitForBuffersSwapped();

    unloadRecycleBuffer();  //This function modifies one buffer
    loadDisplayBuffer();    ///This function modifies another buffer

    waitForRenderFinished();

    renderThread.postTask(swapBuffersAndRender);
}

Поток рендеринга выбран для выполнения задачи замены буферов так, чтобы поток логики игры мог выполнять задачи, которые не изменяют буферы. В моем коде я объединяю задачу обмена буферов и рендеринга и определяю его как объект Runnable, который публикуется в обработчике потока рендеринга. В псевдокоде этот runnable выглядит примерно так:

{
    //Swap the buffers
}
gameThread.notifyThatBuffersSwapped();
{
    //Render items to screen
}
gameThread.notifyThatItemsRendered();

Моя проблема - проблема реализации. Я знаком с концепциями обработчиков, блоков синхронизации и ReentrantLocks. Мне известны методы Lock.await() Lock.signal(), но я нахожу документацию недостаточной, когда пытаюсь понять, как они ведут себя при вызове в цикле итерации.

Как реализовать ReentrantLocks, чтобы два потока ожидали друг друга таким образом? Пожалуйста, включите практическую идиому в ваш ответ, если это возможно.

1 ответ

Я не уверен, что замок это то, что вы хотите. Вы хотите, чтобы текущий поток имел монопольный доступ к объектам, но как только вы снимаете блокировку, вы хотите быть уверены, что другой поток выполнится до того, как вы снова получите блокировку.

Вы могли бы, например, использовать плохо названное ConditionVariable:

loop:
game thread: does stuff w/objects
game thread: cv1.close()
game thread: cv2.open()
[render thread now "owns" the objects]
game thread: does stuff w/o objects
game thread: cv1.block()
game thread: [blocks]

loop:
render thread: does stuff w/objects
render thread: cv2.close()
render thread: cv1.open()
[game thread now "owns" the objects]
render thread: cv2.block()
render thread: [blocks]

Это работает два потока в lockstep. Вы получаете некоторый параллелизм при выполнении операций с объектами, не являющимися общими, но это все.

java.util.concurrent обеспечивает CountDownLatch, но это объект с одним выстрелом, который идет вразрез с вашим желанием избежать выделения ресурсов. Фантазии CyclicBarrier делает больше

Это не очень хорошее решение - хотя замки были не совсем то, что вы хотели, они были частью того, что вы хотели, и я покончил с ними здесь. Невозможно посмотреть на код и легко определить, что оба потока не могут работать с объектами одновременно.

Вы можете рассмотреть возможность двойной буферизации объектов. Вам потребовалось бы вдвое больше памяти, но проблемы синхронизации проще: каждый поток по существу имеет свой собственный набор данных для работы, и единственный раз, когда вам нужно сделать паузу, это когда игровой поток хочет поменять местами наборы. Параллельность максимальна. Если игровой поток опаздывает, поток рендеринга просто рисует то, что у него есть снова (если вы используете GLSurfaceView) или пропускает рендеринг. (Вы можете использовать и тройной буфер для повышения пропускной способности, но это увеличивает использование памяти и задержку.)

Другие вопросы по тегам