Видны ли все побочные эффекты задач исполнителя после invokeAll?

Если я отправлю некоторые задачи в Executor с помощью invokeAll, есть ли гарантии, что отправленный поток увидит все побочные эффекты выполнения задачи, даже если я не вызову get() для каждого из возвращенных Future?

С практической точки зрения, казалось бы, это была бы полезная гарантия, но я ничего не вижу в javadoc.

Точнее, все ли действия в теле Callable, переданного исполнителю, происходят до возврата из вызова invokeAll()?

Раздражает бесполезный вызов get() для каждого будущего, когда на самом деле возвращаемый тип Void и исключения не выбрасываются - вся работа в процессе происходит как побочные эффекты.


person BeeOnRope    schedule 14.09.2011    source источник
comment
Для будущих блуждающих читателей я просто хотел добавить что-то, что помогло бы мне понять это немного лучше. Официальные документы оракула по отношениям «происходит до» в JVM находятся здесь, в разделе «Свойства согласованности памяти»: docs.oracle.com/en/java/javase/16/docs/api/java.base/java/util/ в разделе «Свойства согласованности памяти». Это также относится к ExecutorService, Callable и Future и их отношениям «происходит до». Спасибо, что задали этот вопрос. Это помогло ответить и на мой.   -  person junkie    schedule 14.07.2021


Ответы (2)


Если invokeAny() обещает, что к моменту возврата invokeAny() все еще не выполняются никакие задачи, это будет так: все побочные эффекты видны.

Чтобы invokeAny() знал, что все задачи выполнены, он должен быть синхронизирован с этими потоками, а это означает, что возврат функций происходит после завершения задач (и всего, что происходит в задаче). Однако API «ExecutorServe» и «Future.cancel()» явно не говорит, что происходит, когда вы отменяете запущенную задачу (в частности: будет ли cancel() ждать с возвратом, пока задачи не прекратят выполнение. Тот факт, что после вызова cancel(), isDone() должен возвращать true, подразумевает, что cancel() не вернется до тех пор, пока задача фактически не завершит выполнение.

Еще одна вещь, на которую следует обратить внимание, это то, что вы не будете знать, начала ли задача когда-либо выполняться при использовании invokeAny() без проверки объектов Future.

person Thirler    schedule 14.09.2011
comment
ОП спросил о invokeAll(), а не invokeAny() - person parsifal; 14.09.2011
comment
Да, извините - я непоследовательно использовал как invokeAny(), так и invokeAll() в своем первоначальном вопросе, и я исправил это позже. В любом случае, я склонен согласиться с тем, что для того, чтобы invokeAll() знал, что все задачи выполнены, он должен синхронизироваться с каждым потоком, и это, вероятно, будет установлено происходит раньше со всеми более ранними действиями потока. Вы все еще можете представить своего рода «ленивую» реализацию, которая откладывает некоторую синхронизацию до вызова Future.get(), но формулировка, возвращающая список фьючерсов со своим статусом и результатами, когда все завершено, кажется, исключает это. Принято! - person BeeOnRope; 28.09.2011

Из документации ExecutorService:

Действия в потоке перед отправкой задачи Runnable или Callable в ExecutorService происходят до любых действий, предпринятых этой задачей, которые, в свою очередь, происходят до того, как результат будет получен с помощью Future.get().

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

Однако, поскольку вызов get() — единственный способ определить, завершена ли задача или запущена, я все равно буду вызывать ее при каждом Future, независимо от гарантий памяти.

person parsifal    schedule 14.09.2011
comment
Ну в таком случае можно предположить, что мой Callable не может кинуть. Я не думаю, что существует допустимая концепция последней задачи, поскольку, хотя вы отправляете задачи в некотором порядке и возвращаете Futures в том же порядке, порядок их завершения не определен, поскольку некоторые задачи, конечно, могут выполняться дольше, чем другие. - person BeeOnRope; 28.09.2011