Вы используете устаревший браузер. Этот и другие сайты могут отображаться в нем неправильно. Необходимо обновить браузер или попробовать использовать другой.
Этот сайт использует файлы cookie. Продолжая пользоваться данным сайтом, Вы соглашаетесь на использование нами Ваших файлов cookie. Узнать больше.
Вы находитесь в сообществе Rubukkit. Мы - администраторы серверов Minecraft, разрабатываем собственные плагины и переводим на различные языки плагины наших коллег из других стран.
[Решено] Корректна ли синхронизация? (async -> sync -> async)
UPDATE: Тесты (ниже) показали, что это действительно хороший рабочий вариант. Можно пользоваться.
Вдруг одолели сомнения по поводу алгоритма: параллельный поток (Thread) хочет сделать синхронную работу (обращаться к Bukkit API) в основном серверном цикле, после чего продолжить своё выполнение, используя результаты синхронного выполнения. Привожу тело метода public void run() этого потока:
Java:
/* SOMEWHERE IN THE MAIN SERVER THREAD ... */
...
final Thread thread = new Thread()
{
@Override
public void run()
{
...
/* Do first parallel part HERE */
...
final Runnable syncTask = new Runnable()
{
@Override
public synchronized void run()
{
...
/* Do some sync work with API HERE */
...
notify();
}
};
try
{
synchronized(syncTask)
{
plugin.getServer().getScheduler().runTask(plugin, syncTask);
syncTask.wait();
}
} catch(InterruptedException ex) {
}
...
/* Do last parallel part HERE */
...
}
};
// Start execution
thread.start();
...
/* CONTINUE WITH MAIN THREAD */
Корректно ли это написано? Фактически никогда ранее не пользовался wait/notify, мне ближе мьютексы и критические секции =\
Вдруг одолели сомнения по поводу алгоритма: параллельный поток хочет сделать синхронную работу в основном серверном цикле и продолжить своё выполнение:
Java:
...
/* DO FIRST PARALLEL PART HERE */
...
final Runnable syncTask = new Runnable()
{
@Override
public synchronized void run()
{
...
/* DO SOME SYNC WORK WITH API HERE */
...
notify();
}
};
try
{
synchronized(syncTask)
{
plugin.getServer().getScheduler().runTask(plugin, syncTask);
syncTask.wait();
}
} catch(InterruptedException ex) {
}
...
/* DO LAST PARALLEL PART HERE */
...
Корректно ли это написано? Редко пользуюсь wait/notify, мне ближе мьютексы и критические секции =\
По исходникам видно, что вызывается обычная задача, а я что-то не припоминаю, чтобы обычные задачи были многопоточными.
Т.е. bukkit работает в один поток и вызывает эти задачки друг за дружкой, не требуя синхронизации.
Другое дело Async-Task - это уже полноценная обёртка над Thread t = new Thred(){}; t.run().
Короче, Async Task - это Обычный Thread.run() и они требуют синхронизации и в них нельзя использовать Bukkit Api.
В том случае, что ты написал выше по идее никаких wait'ов не нужно и никаких синхронный штучек, это просто спрятанный вызов обычного метода. Это то, как я понимаю данную ситуацию.
В том случае, что ты написал выше по идее никаких wait'ов не нужно и никаких синхронный штучек, это просто спрятанный вызов обычного метода. Это то, как я понимаю данную ситуацию.
То, что написано выше, является телом параллельного к основному потока, создание которого опущено для простоты.
Пример: потенциально длительное обращение к , небольшая работа с апбдерез шедапишедулер ожиданием окончания его работы, затем опять длительная операция. Понятно, что модно разбить на два потока, но так короче и кошернее, казалось бы. Сраный ввод на телефоне :/
То, что написано выше, является телом параллельного к основному потока, создание которого опущено для простоты.
Пример: потенциально длительное обращение к , небольшая работа с апбдерез шедапишедулер ожиданием окончания его работы, затем опять длительная операция. Понятно, что модно разбить на два потока, но так короче и кошернее, казалось бы. Сраный ввод на телефоне :/
runTask блокируется на время выполнения Runnable на ближайшем серверном тике, а scheduleTask регистрирует его на выполнение и немедленно возвращает? Тогда это решает поставленный вопрос, да. Опять же, в самом баккит апи все написано не так:
runTask(Plugin plugin, Runnable task)
Returns a task that will run on the next server tick.
Такое чувство, что результат их вызова одинаков — Runnable планируется на ближайший тик, а текущий поток побежал дальше.
Добавлено.
Итак, наступило мудрое утро, проводим тестирование.
Я открыл чистый каркас шаблона плагина и написал такой класс:
Java:
package org.rubukkit.template;
public class SyncTest
{
private final BukkitPluginMain plugin;
public SyncTest(final BukkitPluginMain plugin)
{
this.plugin = plugin;
}
public void runTest()
{
BukkitPluginMain.consoleLog.info("STARTING PARALLEL THREAD");
new Thread()
{
@Override
public void run()
{
BukkitPluginMain.consoleLog.info("PARALLEL THREAD HAS BEEN STARTED");
final Runnable r1 = new Runnable()
{
@Override
public void run()
{
BukkitPluginMain.consoleLog.info("SOME SYNC WORK");
try
{
Thread.sleep(5000); // 5 sec
} catch(InterruptedException ex) {
}
}
};
plugin.getServer().getScheduler().runTask(plugin, r1);
BukkitPluginMain.consoleLog.info("PARALLEL THREAD CONTINUES AFTER runTask");
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, r1);
BukkitPluginMain.consoleLog.info("PARALLEL THREAD CONTINUES AFTER scheduleSyncDelayedTask");
BukkitPluginMain.consoleLog.info("PARALLEL THREAD HAS BEEN FINISHED");
}
}.start();
}
}
В onEnable() плагина запускаем тест: new SyncTask(this).runTest();
Проводим тестирование на чистом сервере, в папке plugins лежит только этот плагин. Лог запуска:
Код:
[11:01:35] [Server thread/INFO]: Starting minecraft server version 1.8
[11:01:37] [Server thread/INFO]: [template] Enabling template v0.2.8
[11:01:37] [Server thread/INFO]: STARTING PARALLEL THREAD
[11:01:37] [Thread-8/INFO]: PARALLEL THREAD HAS BEEN STARTED
[11:01:37] [Thread-8/INFO]: PARALLEL THREAD CONTINUES AFTER runTask
[11:01:37] [Thread-8/INFO]: PARALLEL THREAD CONTINUES AFTER scheduleSyncDelayedTask
[11:01:37] [Thread-8/INFO]: PARALLEL THREAD HAS BEEN FINISHED
[11:01:37] [Server thread/INFO]: [RBTemplate] Plugin has been successfully enabled.
[11:01:37] [Server thread/INFO]: Done (1,209s)! For help, type "help" or "?"
[11:01:37] [Server thread/INFO]: SOME SYNC WORK
[11:01:42] [Server thread/INFO]: SOME SYNC WORK
Как можно заметить, совет, данный @ptnk выше, не работает. И runTask, и scheduleSyncDelayedTask выполняют одинаковую работу — немедленно помещают указанный Runnable в очередь исполнения и возвращают управление.
Далее мне интересен вариант, предложенный мной в первом пост темы. Он по факту работает в моём плагине, но даже если не работает — я этого мог раньше не замечать. Поэтому тестирование в лабораторных условиях считаю уместным. Дописываем тест:
Java:
public void runTest()
{
BukkitPluginMain.consoleLog.info("STARTING PARALLEL THREAD");
new Thread()
{
@Override
public void run()
{
BukkitPluginMain.consoleLog.info("PARALLEL THREAD HAS BEEN STARTED");
final Runnable r1 = new Runnable()
{
@Override
public void run()
{
BukkitPluginMain.consoleLog.info("SOME SYNC WORK (r1)");
try
{
Thread.sleep(5000); // 5 sec
} catch(InterruptedException ex) {
}
}
};
final Runnable r2 = new Runnable()
{
@Override
public void run()
{
BukkitPluginMain.consoleLog.info("SOME SYNC WORK (r2)");
try
{
Thread.sleep(5000); // 5 sec
} catch(InterruptedException ex) {
}
synchronized(this)
{
notify();
}
}
};
plugin.getServer().getScheduler().runTask(plugin, r1);
BukkitPluginMain.consoleLog.info("PARALLEL THREAD CONTINUES AFTER runTask");
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, r1);
BukkitPluginMain.consoleLog.info("PARALLEL THREAD CONTINUES AFTER scheduleSyncDelayedTask");
try
{
synchronized(r2)
{
plugin.getServer().getScheduler().runTask(plugin, r2);
r2.wait();
}
} catch(InterruptedException ex) {
}
BukkitPluginMain.consoleLog.info("PARALLEL THREAD CONTINUES AFTER syncronized runTask");
BukkitPluginMain.consoleLog.info("PARALLEL THREAD HAS BEEN FINISHED");
}
}.start();
}
Лог запуска:
Код:
[11:15:51] [Server thread/INFO]: Starting minecraft server version 1.8
[11:15:53] [Server thread/INFO]: [template] Enabling template v0.2.8
[11:15:53] [Server thread/INFO]: STARTING PARALLEL THREAD
[11:15:53] [Thread-7/INFO]: PARALLEL THREAD HAS BEEN STARTED
[11:15:53] [Thread-7/INFO]: PARALLEL THREAD CONTINUES AFTER runTask
[11:15:53] [Thread-7/INFO]: PARALLEL THREAD CONTINUES AFTER scheduleSyncDelayedTask
[11:15:53] [Server thread/INFO]: [RBTemplate] Plugin has been successfully enabled.
[11:15:53] [Server thread/INFO]: Done (1,160s)! For help, type "help" or "?"
[11:15:53] [Server thread/INFO]: SOME SYNC WORK (r1)
[11:15:58] [Server thread/INFO]: SOME SYNC WORK (r2)
[11:16:03] [Server thread/INFO]: SOME SYNC WORK (r1)
[11:16:03] [Thread-7/INFO]: PARALLEL THREAD CONTINUES AFTER syncronized runTask
[11:16:03] [Thread-7/INFO]: PARALLEL THREAD HAS BEEN FINISHED
Как мы видим, параллельный поток дождался выполнения шедулером синхронного Runnable, и только затем продолжил своё выполнение. Дополнительный факт, что один из r1 запустился после него. Это говорит о том, что а) все r1 и r2 были запланированы на один тик, и б) либо они хранятся в Set и берутся оттуда в произвольном порядке, либо Runnable-ы переданные в runTask и в scheduleSyncDelayedTask хранятся в различных полях и, соответственно, имеют предопределённую очерёдность выполнения. P.S. Несколько различных вариаций запуска подтвердили, что:
Выглядит очень похоже на то, что все запланированные на определённый тик Runnable хранятся в HashSet и извлекаются из него беспорядочно.
У Runnable достаточно пометить метод public syncronized void run() вместо употребления в его теле блока syncronized(this) {}. Собственно, этот вариант и был придуман исходно, все тесты подтверждают его работоспособность.
Считаю тему закрытой, спасибо всем, кто хотя бы прочитает её в будущем.
runTask шедулит исполнение и сразу же возвращает управление потоку вызвавшему ему. Для трюков типа ждать окончания выполнения из другого потока надо писать свои костыли либо callSyncTask(...).get().
И все таски хранятся в одной очереди, односвязный список с хвостом в виде AtomicReference.
callSyncMethod(Plugin plugin, Callable<T> task) — Calls a method on the main thread and returns a Future object.
Ну да, тоже вариант, по-своему лаконичный. Спасибо.
У Runnable достаточно пометить метод public syncronized void run() вместо употребления в его теле блока syncronized(this) {}. Собственно, этот вариант и был придуман исходно, все тесты подтверждают его работоспособность..
Синхронизировать большие методы таким образом неэффективно.
То, что ты там написал в спойлере - это гавно. Thread.sleep(5000);// 5 sec - это тормозит основной поток. Представляешь, если у тебя игроки зафризятся?
Книга называется Java Concurrency In Practice, это для тех, кто хочет получше понять, как работать с многопоточностью в java.
Runnable.run() и Thread.start() - это различные вещи, пока, ты не особо понимаешь очевидных вещей, похоже ты даже не знаешь основных проблем, которые возникают в многопоточном программировании и из-за этого ты делаешь неправильные вещи.
Скорее всего что-то типа очереди где поток выполняет тяжёлые вычисления и с некоторым интервалом/количеством тасков выполняет что-то что не может выполняться асинхронно. P.S. Код тса хоть и пашет, но может обломаться если notify произойдёт раньше чем wait, а такое вполне может быть.
Ну, конечно корректнее ставить synchronized(this) { notify(); } в самом конце тела Runnable, но если учесть во внимание, что ждёт его только один поток, и ждёт именно самого конца выполнения, то разница в подходах очень размывается.
То, что ты там написал в спойлере — это гавно. Thread.sleep(5000); // 5 sec - это тормозит основной поток. Представляешь, если у тебя игроки зафризятся?
Представляешь, 5 секунд достаточно много, чтобы увидеть в логе сервера такую разницу по времени, за которую гарантированно произойдёт множество переключений контекста. Говно — потому что не работает так, как я заложил в него логику, или по любой другой причине?
К сожалению, я не могу посоветовать тебе какую-либо книгу. Если такая существует, она должна называться "Как научиться понимать других людей". Я нисколько не сомневаюсь, что ты, возможно, намного дольше меня программируешь на Java, и знаешь больше хороших практик, чем я, но, пожалуйста, попробуй сначала прочитать и понять вопрос, попробовать на него ответить, а не искать в нём подложку из отборной бредятины.
Я сразу написал в первом посту, что это параллельный поток, я прекрасно понимаю где код какого потока, и я никогда не собирался фризить сервер на 5 секунд.
Runnable.run() и Thread.start() — это различные вещи, пока, ты не особо понимаешь очевидных вещей, похоже ты даже не знаешь основных проблем, которые возникают в многопоточном программировании и из-за этого ты делаешь неправильные вещи.
Я уверен, что делаю на 90-99% правильные вещи. Я прошу тебя опровергнуть свои же слова, если ты на самом деле прочитал код этого небольшого теста и вник в его логику, а не пишешь мне что я ничего не знаю "на отъебись".
Прикладываю полноценный компилируемый и запускаемый исходник + .jar.
Задача родилась фактически на пустом месте. Она не является неразрешимой, и если бы я понятия не имел о том, что вообще значат ключевые слова wait/notify, я бы обошёл эту проблему за пять минут.
Относись к ней, как к учебной, не более.
Представляешь, 5 секунд достаточно много, чтобы увидеть в логе сервера такую разницу по времени, за которую гарантированно произойдёт множество переключений контекста. Говно — потому что не работает так, как я заложил в него логику, или по любой другой причине?
Я сразу написал в первом посту, что это параллельный поток, я прекрасно понимаю где код какого потока, и я никогда не собирался фризить сервер на 5 секунд.
Я тебе уже в четвёртый раз пишу, что никаких Bukkit-API во внешних потоках быть не должно, если нигде в документации не написано специально "thread-safety)".
Я тебе пишу про это, потому, что у меня нет уверенности, что CraftScheduler обладает таким свойством, потому, что оффициальная документация недоступна.
Для меня все синхронные таски в bukkit это Runnable.run(), т.е. вызов метода в том же самом потоке.
А вот все асинхронные таски в bukkit Thread.start(), т.к. метод крутиться в соседнем потоке [Нет нормальных исходников, чтобы проследить, где там и как запускаются BukkitWorker, но у меня ощущение, что именно Thread.start() для них делается, поэтому они и не Thread-Safety и поэтому они Deprecated и написано, чтобы не использовали в них Bukkit-Api].
На мой взгляд то, что у тебя в примере написано таит в себе скрытую угрозу т.к. CraftScheduler может быть не потоко безопасным, поэтому и Bukkit Api из других потоков использовать нельзя из-за возможности нарваться на эксепшены, а это значит, что нужно искать другие стратегии для решения твоей потребности.
Если у тебя всегда строго какой-то ресурс нужно разделить между двумя потоками, то почему бы просто не воспользоваться коллекцией из conurrency-пакета? Один поток на чтение и один поток на запись синхронизированной коллекции проблем не вызывут и никаких лишних телодвижений не будет.
Поэтому и нужно предварительно озадачиться вопросом многопоточности, изучить стратегии и паттерны.
Я тебе уже в четвёртый раз пишу, что никаких Bukkit-API во внешних потоках быть не должно, если нигде в документации не написано специально "thread-safety".
У меня нет ни одного вызова API вне потока главного цикла сервера, кроме вызовов методов шедулера (ну и логгера, сделаем на него скидку для простоты ).
Они синхронны с тем потоком, который их вызвал, а с bukkit — если их вызвали в главном потоке. Хоть ты, хоть шедулер. Простое забивание процессорного времени и стека мусорными прологом и эпилогом метода.
Само существование шедулера как раз таки оправдано, только если он thread-safety. Не думаю, что в своё время грамотные программисты придумали его для планирования только из главного потока. BukkitWorker — вообще не пользуюсь, просто обёртка над Thread, как и BukkitRunnable над Runnable. Достаточно только самого шедулера.
Ладно, предлагаю закрыть дискуссию, и считать, что мы друг друга поняли.
В преддверии глобального обновления, мы проводим исследования, которые помогут нам сделать опыт пользования форумом ещё удобнее. Помогите нам, примите участие!