Как синхронизировать общие данные между потоками, используя паузу, затем возобновление или альтернативы - PullRequest
2 голосов
/ 31 марта 2012

Я пишу игру, в которой поток - GameThread - зацикливается навсегда, обновляет все мои спрайты, рендерит их, а затем спит некоторое время, прежде чем снова делать все это. У меня также есть специальный обработчик событий, который обрабатывает нажатия клавиш и т. Д.

В большинстве случаев все работает нормально. Однако у меня есть проблема, если событие генерируется во время рендеринга GameThread. В редких случаях обработчик, который имеет дело с событием, может вносить параллельные изменения в то, что должно быть отображено, влияя на результаты рендеринга GameThread.

Чтобы избежать этого, я хочу, чтобы обработчик событий немедленно приостановил GameThread, обработал событие и затем возобновил GameThread.

  1. Методы suspend() / resume() удовлетворяют моим потребностям, но они устарели. В моем случае, однако, поскольку вероятность тупиковых ситуаций мала, безопасно ли их использовать независимо от этого?

  2. Если нет, какие у меня есть альтернативы, которые не требуют огромных накладных расходов?

Я видел предложение запросить приостановку потока, установив флаг в Потоке для приостановки. В моем случае, однако, я не считаю это приемлемым вариантом, поскольку цикл GameThread может занять некоторое время во время итерации цикла. Я не смогу проверить флаг, пока не закончу с циклом, и к тому времени будет слишком поздно.

Мне нужна немедленная пауза, иначе пользователь заметит задержку в обработке события.

Ответы [ 3 ]

1 голос
/ 31 марта 2012

Обычно вы выполняете некоторую синхронизацию потоков:

Это позволит вам выполнить одно из двух действий: выполнить рендеринг в потоке рендеринга игры или внести изменения в зависимости от ваших событий.

Похоже, проблема, с которой вы сталкиваетесь, заключается в том, что вашему коду рендеринга требуется слишком много времени для того, чтобы вы на самом деле имели плавный опыт (т.е. многие события могут накапливаться для обработки, пока вы что-то рендерите). В этом случае вы должны делать рендеринг из независимых частей, которые могут быстро заканчиваться и синхронизироваться с ними.

Без какого-либо кода я не могу дать вам конкретный совет, однако в целом это будет выглядеть примерно так:

List<Shape> shapesToRender;
Object lockObject = new Object(); // Note this must be somehow shared between two threads

// Your rendering thread method
public void renderForever() {
  while(true) {
    for(Shape shape: shapesToRender) {
      synchronized(lockObject) {
        render(shape);
      }
    }
  }
}

// One of your event handlers
public void handleEvent(Event event) {
  synchronized(lockObject) {
    // Process event somehow, e.g. change the color of some of the shapes
    event.getShape().setColor(Color.RED);
  }
}

С учетом вышеизложенного либо:

  • Вы будете рендерить одну фигуру (и все ваши обработчики событий будут ждать ее завершения), или
  • Некоторые из ваших обработчиков событий будут что-то делать (и ваш поток рендеринга будет ждать, пока это не закончится)

Вы должны взглянуть на этот след Java более подробно:

как и другие решения, например, используя объекты блокировки:

или одновременных коллекций:

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

Надеюсь, это поможет.

1 голос
/ 31 марта 2012

Если вы хотите синхронизировать доступ к ресурсам, используйте ReentrantLock:

ReentrantLock sync = new ReentrantLock ();

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

Затем в каждом месте, к которому вы обращаетесь к рассматриваемому ресурсу, вы будете использовать этот объект общей блокировки, а также получать и снимать блокировку (т. е. критические разделы):

sync.lock();

try {
  // critical section code here
}
finally {
  sync.unlock();
}

Это довольно стандартное параллельное программирование в Java.Помните, что «блокировка» - это метод блокировки, поэтому вы можете вместо этого использовать «tryLock», который позволяет вам попытаться получить блокировку, но возвращает логическое значение относительно того, действительно ли вы получили блокировку:

 if (sync.tryLock()) {
   try {
     //critical section
   }
   finally {
     sync.unlock();
   }
 }

Существует версия tryLock, которая будет ожидать определенное количество времени, прежде чем перестанет пытаться получить блокировку и вернуть ложное значение.

0 голосов
/ 31 марта 2012

Методы suspend () / resume () соответствуют моим потребностям, но они устарели.В моем случае, однако, поскольку вероятность возникновения тупика практически отсутствует, безопасно ли их использовать независимо от этого?

Очевидно, что при нулевой вероятности возникновения тупика это безопасно.Но есть много неожиданных способов зайти в тупик.Например, вы могли бы приостановить поток, пока он инициализирует класс ... и это заблокировало бы любой другой поток, пытающийся обратиться к статическому полю этого класса.(Это является следствием определенного поведения JVM. Есть и другие места, где блокировка / синхронизация, которая происходит под колпаком, не указана. Достаточно справедливо. Этого не должно быть ..., если вы не планируете использоватьэти устаревшие методы.)

Итак, реальность такова, что действительно трудно определить (доказать), действительно ли это безопасно.И если вы не можете определить это, то это потенциально рискованная вещь.Вот почему методы устарели.

(Строго говоря, это не тупик. Это тупик, когда потоки не могут продолжаться. В этом случае другие потоки могут продолжаться, если вы можете возобновить приостановленный поток.)

...