这个问题与我在existing coroutine implementations in Java的问题有关。如果,正如我怀疑的那样,事实证明Java目前没有完整的协同程序实现,那么实现它们需要什么呢?
正如我在那个问题中所说,我知道以下内容:
我将依次解决每个人的不足。
这种“解决方案”是病态的。协同程序的重点是避免线程,锁定,内核调度等的开销。协同程序应该是轻量级的,并且只能在用户空间中执行。以严格限制的全倾斜线程实现它们可以摆脱所有优势。
这个解决方案更实用,虽然有点难以实现。这与在C中的协同程序库中跳转到汇编语言大致相同(这是其中有多少工作),其优点是您只有一个架构可以担心并且正确。
它还将您绑定到仅在完全兼容的JVM堆栈上运行代码(例如,没有Android),除非您可以找到在非兼容堆栈上执行相同操作的方法。但是,如果您确实找到了实现此目的的方法,那么您现在已经将系统复杂性和测试需求翻了一番。
Da Vinci Machine很酷的实验,但由于它不是一个标准的JVM,它的功能无处不在。事实上,我怀疑大多数生产环境都会特别禁止使用达芬奇机器。因此,我可以使用它来进行很酷的实验,但不能用于我期望发布到现实世界的任何代码。
这也有类似于上面的JVM字节码操作解决方案的附加问题:不适用于替代堆栈(如Android)。
这个解决方案在Java中实现这一点的意义重大。 CPU和操作系统的每种组合都需要独立测试,每一种都是可能令人沮丧的微妙故障。或者,当然,我可以将自己完全绑定到一个平台,但这也使得用Java完成工作的重点完全没有用。
因此...
有没有办法在不使用这四种技术之一的情况下在Java中实现协同程序?或者我将被迫使用那些闻起来最少的四个(JVM操作)中的一个?
已编辑添加:
为了确保包含混淆,这是my other one的相关问题,但不一样。那个人正在寻找一个现有的实施,以避免不必要地重新发明轮子。这是一个问题,如果另一个证明无法解决,将如何在Java中实现协同程序。目的是在不同的线程上保留不同的问题。
答案 0 :(得分:31)
我会看一下这个:http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html,它非常有趣,应该提供一个好的起点。但当然我们正在使用Java,所以我们可以做得更好(或者更糟糕的是因为没有宏:))
根据我对协同程序的理解,您通常有生产者和消费者协程(或者至少这是最常见的模式)。但从语义上讲,你不希望生产者给消费者打电话,反之亦然,因为这引入了不对称性。但是考虑到基于堆栈的语言的工作方式,我们需要让某人进行调用。
所以这是一个非常简单的类型层次结构:
public interface CoroutineProducer<T>
{
public T Produce();
public boolean isDone();
}
public interface CoroutineConsumer<T>
{
public void Consume(T t);
}
public class CoroutineManager
{
public static Execute<T>(CoroutineProducer<T> prod, CoroutineConsumer<T> con)
{
while(!prod.IsDone()) // really simple
{
T d = prod.Produce();
con.Consume(d);
}
}
}
现在当然困难的部分是实现接口,特别是很难将计算分解为单个步骤。为此,您可能需要另外一组持久控制结构。基本思想是我们想要模拟非本地控制转移(最后它有点像我们模拟goto
)。我们基本上希望通过将当前操作的状态保持在堆而不是堆栈中来避免使用堆栈和pc
(程序计数器)。因此,我们需要一堆辅助类。
例如:
让我们说在一个理想的世界中你想写一个看起来像这样的消费者(伪代码):
boolean is_done;
int other_state;
while(!is_done)
{
//read input
//parse input
//yield input to coroutine
//update is_done and other_state;
}
我们需要抽象局部变量,如is_done
和other_state
,我们需要抽象while循环本身,因为我们的yield
之类的操作不会使用堆栈。所以让我们创建一个while循环抽象和相关的类:
enum WhileState {BREAK, CONTINUE, YIELD}
abstract class WhileLoop<T>
{
private boolean is_done;
public boolean isDone() { return is_done;}
private T rval;
public T getReturnValue() {return rval;}
protected void setReturnValue(T val)
{
rval = val;
}
public T loop()
{
while(true)
{
WhileState state = execute();
if(state == WhileState.YIELD)
return getReturnValue();
else if(state == WhileState.BREAK)
{
is_done = true;
return null;
}
}
}
protected abstract WhileState execute();
}
这里的基本技巧是将本地变量移动为 class 变量,并将范围块转换为类,这使我们能够“重新进入”我们的'循环'在产生我们的回报价值之后。
现在实现我们的制作人
public class SampleProducer : CoroutineProducer<Object>
{
private WhileLoop<Object> loop;//our control structures become state!!
public SampleProducer()
{
loop = new WhileLoop()
{
private int other_state;//our local variables become state of the control structure
protected WhileState execute()
{
//this implements a single iteration of the loop
if(is_done) return WhileState.BREAK;
//read input
//parse input
Object calcluated_value = ...;
//update is_done, figure out if we want to continue
setReturnValue(calculated_value);
return WhileState.YIELD;
}
};
}
public Object Produce()
{
Object val = loop.loop();
return val;
}
public boolean isDone()
{
//we are done when the loop has exited
return loop.isDone();
}
}
可以为其他基本控制流结构进行类似的技巧。理想情况下,您可以构建这些帮助程序类的库,然后使用它们来实现这些简单的接口,这些接口最终将为您提供协同例程的语义。我确信我在这里写的所有内容都可以概括和扩展。
答案 1 :(得分:7)
我建议你看一下Kotlin coroutines on JVM。不过,它属于不同的类别。没有涉及字节码操作,它也适用于Android。但是,您必须在Kotlin中编写协程。好处是Kotlin专为与Java的互操作性而设计,因此您仍然可以继续使用所有Java库并在同一项目中自由组合Kotlin和Java代码,甚至将它们并排放在相同的目录中包。
此Guide to kotlinx.coroutines提供了更多示例,而the coroutines design文档则解释了所有动机,用例和实现细节。
答案 2 :(得分:3)
我刚刚遇到这个问题,我想提一下,我认为可能以类似的方式实现协同程序或生成器C#。也就是说我实际上并不使用Java,但CIL与JVM有相似的限制。
C#中的yield statement是纯语言特性,不属于CIL字节码。 C#编译器只为每个生成器函数创建一个隐藏的私有类。如果在函数中使用yield语句,则必须返回IEnumerator或IEnumerable。编译器将您的代码“打包”到类似于状态机的类中。
C#编译器可能会在生成的代码中使用一些“goto”,以便更轻松地转换为状态机。我不知道Java字节码的功能,如果有类似普通的无条件跳转,但在“汇编级别”通常是可能的。
如前所述,此功能必须在编译器中实现。因为我对Java及其编译器知之甚少,所以无法判断是否可以改变/扩展编译器,可能使用“预处理器”或其他东西。
我个人喜欢coroutines。作为Unity游戏开发者,我经常使用它们。因为我用ComputerCraft玩很多Minecraft,我很好奇Lua(LuaJ)中的协程是用线程实现的。
答案 3 :(得分:1)
Kotlin使用以下方法进行协同程序 (来自https://kotlinlang.org/docs/reference/coroutines.html):
协同程序完全通过编译技术实现(不需要VM或OS端的支持),并且通过代码转换进行暂停。基本上,每个挂起功能(优化可能适用,但我们不会在此处进行)转换为状态机,其中状态对应于挂起呼叫。在暂停之前,下一个状态存储在编译器生成的类的字段中以及相关的局部变量等。恢复该协程后,本地变量将被恢复,状态机在暂停后立即从状态继续。 / p>
暂停的协程可以作为保持其暂停状态和本地的对象存储和传递。这类对象的类型是Continuation,这里描述的整体代码转换对应于经典的Continuation-passing风格。因此,暂停功能会在引擎盖下使用Continuation类型的额外参数。
在https://github.com/Kotlin/kotlin-coroutines/blob/master/kotlin-coroutines-informal.md
查看设计文档答案 4 :(得分:1)
Loom 项目:https://jdk.java.net/loom/ 将延续引入 Java。 一个例子:
static final ContinuationScope scope=new ContinuationScope("TST");
public static void main(String[] args) {
example1();
}
// *********************************************************************
// *** EXAMPLE 1: Co-routine with three active phases:
// *********************************************************************
public static void example1() {
Continuation coroutine=new Continuation(scope,new Runnable() {
public void run() {
System.out.println("Part 1 - Statements");
Continuation.yield(scope); // DETACH 1
System.out.println("Part 2 - Statements");
Continuation.yield(scope); // DETACH 2
System.out.println("Part 3 - Statements");
}});
coroutine.run(); // Vil utføre Part 1.
System.out.println("Returns here after first DETACH(Yield)");
coroutine.run(); // Vil utføre Part 2.
System.out.println("Returns here after second DETACH(Yield)");
coroutine.run(); // Vil utføre Part 3.
System.out.println("Returns here after 'FINAL END'");
System.out.println("Next line should be: IllegalStateException: Continuation terminated");
coroutine.run(); // IllegalStateException: Continuation terminated
}
答案 5 :(得分:0)
我有一个在Java中使用的Coroutine类。它基于线程和使用线程具有允许并行操作的优点,这在多核机器上可以是一个优势。因此,您可能需要考虑基于线程的方法。
答案 6 :(得分:0)
Java6 +的另一个选择是
pythonic协程实现:
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
class CorRunRAII {
private final List<WeakReference<? extends CorRun>> resources = new ArrayList<>();
public CorRunRAII add(CorRun resource) {
if (resource == null) {
return this;
}
resources.add(new WeakReference<>(resource));
return this;
}
public CorRunRAII addAll(List<? extends CorRun> arrayList) {
if (arrayList == null) {
return this;
}
for (CorRun corRun : arrayList) {
add(corRun);
}
return this;
}
@Override
protected void finalize() throws Throwable {
super.finalize();
for (WeakReference<? extends CorRun> corRunWeakReference : resources) {
CorRun corRun = corRunWeakReference.get();
if (corRun != null) {
corRun.stop();
}
}
}
}
class CorRunYieldReturn<ReceiveType, YieldReturnType> {
public final AtomicReference<ReceiveType> receiveValue;
public final LinkedBlockingDeque<AtomicReference<YieldReturnType>> yieldReturnValue;
CorRunYieldReturn(AtomicReference<ReceiveType> receiveValue, LinkedBlockingDeque<AtomicReference<YieldReturnType>> yieldReturnValue) {
this.receiveValue = receiveValue;
this.yieldReturnValue = yieldReturnValue;
}
}
interface CorRun<ReceiveType, YieldReturnType> extends Runnable, Callable<YieldReturnType> {
boolean start();
void stop();
void stop(final Throwable throwable);
boolean isStarted();
boolean isEnded();
Throwable getError();
ReceiveType getReceiveValue();
void setResultForOuter(YieldReturnType resultForOuter);
YieldReturnType getResultForOuter();
YieldReturnType receive(ReceiveType value);
ReceiveType yield();
ReceiveType yield(YieldReturnType value);
<TargetReceiveType, TargetYieldReturnType> TargetYieldReturnType yieldFrom(final CorRun<TargetReceiveType, TargetYieldReturnType> another);
<TargetReceiveType, TargetYieldReturnType> TargetYieldReturnType yieldFrom(final CorRun<TargetReceiveType, TargetYieldReturnType> another, final TargetReceiveType value);
}
abstract class CorRunSync<ReceiveType, YieldReturnType> implements CorRun<ReceiveType, YieldReturnType> {
private ReceiveType receiveValue;
public final List<WeakReference<CorRun>> potentialChildrenCoroutineList = new ArrayList<>();
// Outside
private AtomicBoolean isStarted = new AtomicBoolean(false);
private AtomicBoolean isEnded = new AtomicBoolean(false);
private Throwable error;
private YieldReturnType resultForOuter;
@Override
public boolean start() {
boolean isStarted = this.isStarted.getAndSet(true);
if ((! isStarted)
&& (! isEnded())) {
receive(null);
}
return isStarted;
}
@Override
public void stop() {
stop(null);
}
@Override
public void stop(Throwable throwable) {
isEnded.set(true);
if (throwable != null) {
error = throwable;
}
for (WeakReference<CorRun> weakReference : potentialChildrenCoroutineList) {
CorRun child = weakReference.get();
if (child != null) {
child.stop();
}
}
}
@Override
public boolean isStarted() {
return isStarted.get();
}
@Override
public boolean isEnded() {
return isEnded.get();
}
@Override
public Throwable getError() {
return error;
}
@Override
public ReceiveType getReceiveValue() {
return receiveValue;
}
@Override
public void setResultForOuter(YieldReturnType resultForOuter) {
this.resultForOuter = resultForOuter;
}
@Override
public YieldReturnType getResultForOuter() {
return resultForOuter;
}
@Override
public synchronized YieldReturnType receive(ReceiveType value) {
receiveValue = value;
run();
return getResultForOuter();
}
@Override
public ReceiveType yield() {
return yield(null);
}
@Override
public ReceiveType yield(YieldReturnType value) {
resultForOuter = value;
return receiveValue;
}
@Override
public <TargetReceiveType, TargetYieldReturnType> TargetYieldReturnType yieldFrom(CorRun<TargetReceiveType, TargetYieldReturnType> another) {
return yieldFrom(another, null);
}
@Override
public <TargetReceiveType, TargetYieldReturnType> TargetYieldReturnType yieldFrom(CorRun<TargetReceiveType, TargetYieldReturnType> another, TargetReceiveType value) {
if (another == null || another.isEnded()) {
throw new RuntimeException("Call null or isEnded coroutine");
}
potentialChildrenCoroutineList.add(new WeakReference<CorRun>(another));
synchronized (another) {
boolean isStarted = another.start();
boolean isJustStarting = ! isStarted;
if (isJustStarting && another instanceof CorRunSync) {
return another.getResultForOuter();
}
return another.receive(value);
}
}
@Override
public void run() {
try {
this.call();
}
catch (Exception e) {
e.printStackTrace();
stop(e);
return;
}
}
}
abstract class CorRunThread<ReceiveType, YieldReturnType> implements CorRun<ReceiveType, YieldReturnType> {
private final ExecutorService childExecutorService = newExecutorService();
private ExecutorService executingOnExecutorService;
private static final CorRunYieldReturn DUMMY_COR_RUN_YIELD_RETURN = new CorRunYieldReturn(new AtomicReference<>(null), new LinkedBlockingDeque<AtomicReference>());
private final CorRun<ReceiveType, YieldReturnType> self;
public final List<WeakReference<CorRun>> potentialChildrenCoroutineList;
private CorRunYieldReturn<ReceiveType, YieldReturnType> lastCorRunYieldReturn;
private final LinkedBlockingDeque<CorRunYieldReturn<ReceiveType, YieldReturnType>> receiveQueue;
// Outside
private AtomicBoolean isStarted = new AtomicBoolean(false);
private AtomicBoolean isEnded = new AtomicBoolean(false);
private Future<YieldReturnType> future;
private Throwable error;
private final AtomicReference<YieldReturnType> resultForOuter = new AtomicReference<>();
CorRunThread() {
executingOnExecutorService = childExecutorService;
receiveQueue = new LinkedBlockingDeque<>();
potentialChildrenCoroutineList = new ArrayList<>();
self = this;
}
@Override
public void run() {
try {
self.call();
}
catch (Exception e) {
stop(e);
return;
}
stop();
}
@Override
public abstract YieldReturnType call();
@Override
public boolean start() {
return start(childExecutorService);
}
protected boolean start(ExecutorService executorService) {
boolean isStarted = this.isStarted.getAndSet(true);
if (!isStarted) {
executingOnExecutorService = executorService;
future = (Future<YieldReturnType>) executingOnExecutorService.submit((Runnable) self);
}
return isStarted;
}
@Override
public void stop() {
stop(null);
}
@Override
public void stop(final Throwable throwable) {
if (throwable != null) {
error = throwable;
}
isEnded.set(true);
returnYieldValue(null);
// Do this for making sure the coroutine has checked isEnd() after getting a dummy value
receiveQueue.offer(DUMMY_COR_RUN_YIELD_RETURN);
for (WeakReference<CorRun> weakReference : potentialChildrenCoroutineList) {
CorRun child = weakReference.get();
if (child != null) {
if (child instanceof CorRunThread) {
((CorRunThread)child).tryStop(childExecutorService);
}
}
}
childExecutorService.shutdownNow();
}
protected void tryStop(ExecutorService executorService) {
if (this.executingOnExecutorService == executorService) {
stop();
}
}
@Override
public boolean isEnded() {
return isEnded.get() || (
future != null && (future.isCancelled() || future.isDone())
);
}
@Override
public boolean isStarted() {
return isStarted.get();
}
public Future<YieldReturnType> getFuture() {
return future;
}
@Override
public Throwable getError() {
return error;
}
@Override
public void setResultForOuter(YieldReturnType resultForOuter) {
this.resultForOuter.set(resultForOuter);
}
@Override
public YieldReturnType getResultForOuter() {
return this.resultForOuter.get();
}
@Override
public YieldReturnType receive(ReceiveType value) {
LinkedBlockingDeque<AtomicReference<YieldReturnType>> yieldReturnValue = new LinkedBlockingDeque<>();
offerReceiveValue(value, yieldReturnValue);
try {
AtomicReference<YieldReturnType> takeValue = yieldReturnValue.take();
return takeValue == null ? null : takeValue.get();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
@Override
public ReceiveType yield() {
return yield(null);
}
@Override
public ReceiveType yield(final YieldReturnType value) {
returnYieldValue(value);
return getReceiveValue();
}
@Override
public <TargetReceiveType, TargetYieldReturnType> TargetYieldReturnType yieldFrom(final CorRun<TargetReceiveType, TargetYieldReturnType> another) {
return yieldFrom(another, null);
}
@Override
public <TargetReceiveType, TargetYieldReturnType> TargetYieldReturnType yieldFrom(final CorRun<TargetReceiveType, TargetYieldReturnType> another, final TargetReceiveType value) {
if (another == null || another.isEnded()) {
throw new RuntimeException("Call null or isEnded coroutine");
}
boolean isStarted = false;
potentialChildrenCoroutineList.add(new WeakReference<CorRun>(another));
synchronized (another) {
if (another instanceof CorRunThread) {
isStarted = ((CorRunThread)another).start(childExecutorService);
}
else {
isStarted = another.start();
}
boolean isJustStarting = ! isStarted;
if (isJustStarting && another instanceof CorRunSync) {
return another.getResultForOuter();
}
TargetYieldReturnType send = another.receive(value);
return send;
}
}
@Override
public ReceiveType getReceiveValue() {
setLastCorRunYieldReturn(takeLastCorRunYieldReturn());
return lastCorRunYieldReturn.receiveValue.get();
}
protected void returnYieldValue(final YieldReturnType value) {
CorRunYieldReturn<ReceiveType, YieldReturnType> corRunYieldReturn = lastCorRunYieldReturn;
if (corRunYieldReturn != null) {
corRunYieldReturn.yieldReturnValue.offer(new AtomicReference<>(value));
}
}
protected void offerReceiveValue(final ReceiveType value, LinkedBlockingDeque<AtomicReference<YieldReturnType>> yieldReturnValue) {
receiveQueue.offer(new CorRunYieldReturn(new AtomicReference<>(value), yieldReturnValue));
}
protected CorRunYieldReturn<ReceiveType, YieldReturnType> takeLastCorRunYieldReturn() {
try {
return receiveQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
protected void setLastCorRunYieldReturn(CorRunYieldReturn<ReceiveType,YieldReturnType> lastCorRunYieldReturn) {
this.lastCorRunYieldReturn = lastCorRunYieldReturn;
}
protected ExecutorService newExecutorService() {
return Executors.newCachedThreadPool(getThreadFactory());
}
protected ThreadFactory getThreadFactory() {
return new ThreadFactory() {
@Override
public Thread newThread(final Runnable runnable) {
Thread thread = new Thread(runnable);
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
throwable.printStackTrace();
if (runnable instanceof CorRun) {
CorRun self = (CorRun) runnable;
self.stop(throwable);
thread.interrupt();
}
}
});
return thread;
}
};
}
}
现在你可以用这种方式使用pythonic协程 (例如斐波纳契数)
线程版本:
class Fib extends CorRunThread<Integer, Integer> {
@Override
public Integer call() {
Integer times = getReceiveValue();
do {
int a = 1, b = 1;
for (int i = 0; times != null && i < times; i++) {
int temp = a + b;
a = b;
b = temp;
}
// A pythonic "yield", i.e., it returns `a` to the caller and waits `times` value from the next caller
times = yield(a);
} while (! isEnded());
setResultForOuter(Integer.MAX_VALUE);
return getResultForOuter();
}
}
class MainRun extends CorRunThread<String, String> {
@Override
public String call() {
// The fib coroutine would be recycled by its parent
// (no requirement to call its start() and stop() manually)
// Otherwise, if you want to share its instance and start/stop it manually,
// please start it before being called by yieldFrom() and stop it in the end.
Fib fib = new Fib();
String result = "";
Integer current;
int times = 10;
for (int i = 0; i < times; i++) {
// A pythonic "yield from", i.e., it calls fib with `i` parameter and waits for returned value as `current`
current = yieldFrom(fib, i);
if (fib.getError() != null) {
throw new RuntimeException(fib.getError());
}
if (current == null) {
continue;
}
if (i > 0) {
result += ",";
}
result += current;
}
setResultForOuter(result);
return result;
}
}
同步(非线程)版本:
class Fib extends CorRunSync<Integer, Integer> {
@Override
public Integer call() {
Integer times = getReceiveValue();
int a = 1, b = 1;
for (int i = 0; times != null && i < times; i++) {
int temp = a + b;
a = b;
b = temp;
}
yield(a);
return getResultForOuter();
}
}
class MainRun extends CorRunSync<String, String> {
@Override
public String call() {
CorRun<Integer, Integer> fib = null;
try {
fib = new Fib();
} catch (Exception e) {
e.printStackTrace();
}
String result = "";
Integer current;
int times = 10;
for (int i = 0; i < times; i++) {
current = yieldFrom(fib, i);
if (fib.getError() != null) {
throw new RuntimeException(fib.getError());
}
if (current == null) {
continue;
}
if (i > 0) {
result += ",";
}
result += current;
}
stop();
setResultForOuter(result);
if (Utils.isEmpty(result)) {
throw new RuntimeException("Error");
}
return result;
}
}
执行(两个版本都有效):
// Run the entry coroutine
MainRun mainRun = new MainRun();
mainRun.start();
// Wait for mainRun ending for 5 seconds
long startTimestamp = System.currentTimeMillis();
while(!mainRun.isEnded()) {
if (System.currentTimeMillis() - startTimestamp > TimeUnit.SECONDS.toMillis(5)) {
throw new RuntimeException("Wait too much time");
}
}
// The result should be "1,1,2,3,5,8,13,21,34,55"
System.out.println(mainRun.getResultForOuter());
答案 7 :(得分:0)
在Java上还有Quasar,在Oracle上还有Project Loom,其中对JVM进行了光纤和延续的扩展。这是Youtoube上的presentation of Loom。还有更多。只需搜索即可轻松找到。