Implementacija coroutina u Javi

Ovo je pitanje povezano s mojim pitanjem o postojećim koroutinskim implementacijama u Javi . Ako se, kao što pretpostavljam, ispostavi da trenutno Java nema potpunu implementaciju koroutina koje će biti potrebne za njihovu implementaciju?

Kao što sam rekao u ovom pitanju, znam sljedeće:

  • Koroutine možete implementirati kao potoke / stream bazene iza scene.
  • Možete napraviti složene stvari s JVM bajtnim kodom iza kulisa kako biste omogućili koroutine.
  • U JVM implementaciji "Da Vinci Machine", implementiraju se primitivi koji čine coroutines izvršnim bez bajtkoda.
  • Tu su i razni JNI pristupi koroutinima.

S druge strane, razmotrit ću svaku manu.

tematski utemeljeni suroutini

Ovo "rješenje" je patološko. Cjelokupna svrha coroutines je izbjegavanje opterećenja niti, zaključavanje, planiranje jezgre itd. Coroutines bi trebao biti lagan i brz i raditi samo u korisničkom prostoru. Njihovo uvođenje s gledišta tokova s ​​punim nagibom s čvrstim ograničenjima eliminira sve prednosti.

JVM upravljanje bajtnim kodom

Ovo rješenje je praktičnije, iako ga je malo teško ukloniti. To je otprilike isto kao i prelazak na asemblerski jezik za C-biblioteke koje se koriste za kopiranje (koliko oni rade), uz prednost da imate samo jednu arhitekturu za koju morate brinuti i dobiti pravo.

Također vas povezuje samo s pokretanjem koda na potpuno kompatibilnim JVM stackovima (što znači, na primjer, bez Androida), ako ne možete pronaći način da to učinite u neprikladnom slogu. Međutim, ako ste pronašli način da to učinite, sada ste udvostručili svoje složenosti i potrebe sustava.

Da Vinci stroj

Da Vinci stroj je super za eksperimentiranje, ali budući da nije standardni JVM, njegove značajke neće biti dostupne svugdje. Doista, pretpostavljam da većina uvjeta proizvodnje izričito zabranjuje uporabu Da Vinci stroja. Mogao bih ga iskoristiti za kul eksperimente, ali ne i za bilo koji kod koji očekujem da ću objaviti u stvarnom svijetu.

To također ima dodatni problem, slično gore opisanom rješenju za upravljanje bajtnim kodom JVM: neće raditi na alternativnim skupovima (na primjer, Androidu).

Implementacija JNI

Ovo rješenje čini sve napore za to u Javi. Svaka kombinacija procesora i operativnog sustava zahtijeva neovisno testiranje, a svaka od njih je potencijalno frustrirajuća zbog slabog kvara. Alternativno, naravno, mogao bih se u potpunosti povezati s jednom platformom, ali to također čini činjenje stvari na Javi potpuno kontroverznim.

Dakle ...

Postoji li način za provedbu coroutines u Java bez korištenja jedne od ove četiri metode? Ili ću biti prisiljen koristiti jednu od tih četiri koja najmanje smrdi (JVM keying)?


Uređeno za dodavanje:

Samo da bi se uvjerili da je zbunjenost sadržana, ovo je povezano pitanje za moju drugu , ali ne za istu. Ovaj korisnik traži postojeću postojeću implementaciju kako bi se izbjeglo ponovno osmišljavanje kotača. To je pitanje koje se odnosi na način na koji možete implementirati Java coroutines ako se drugi pokaže nepobitnim. Cilj je postaviti različita pitanja o različitim temama.

49
17 мая '10 в 7:26 2010-05-17 07:26 SAMO ispravno MIŠLJENJE postavite 17. svibnja '10 u 7:26 2010-05-17 07:26
@ 6 odgovora

Ja bih pogledao ovo: http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html , vrlo zanimljiv i trebao bi biti dobro mjesto za početak. Ali, naravno, koristimo Javu tako da možemo bolje (ili možda još gore, jer nema makronaredbi))

Iz mog razumijevanja s coroutines, obično koristite proizvođača i potrošača coroutines (ili barem to je najčešći uzorak). No, semantički, ne želite da proizvođač nazove potrošača ili obrnuto, jer uvodi asimetriju. No, s obzirom na to kako rade jezici temeljeni na stogu, treba nam netko koga treba nazvati.

Dakle, ovdje je vrlo jednostavna vrsta hijerarhije:

 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); } } } 

Sada, naravno, težak dio implementira sučelja, posebno, teško je podijeliti izračun u odvojene faze. Da biste to učinili, vjerojatno ćete trebati cijeli niz stalnih upravljačkih struktura . Osnovna ideja je da želimo simulirati ne-lokalni prijenos kontrole (na kraju krajeva, to je slično činjenici da modeliramo goto ). U osnovi želimo prestati koristiti stog i pc (programski brojač), zadržavajući stanje naših trenutnih operacija na hrpi, a ne na stog. Trebamo gomilu pomoćnih satova.

Na primjer:

Recimo da ste u idealnom svijetu željeli napisati potrošača sličnog ovome (psuedocode):

 boolean is_done; int other_state; while(!is_done) { //read input //parse input //yield input to coroutine //update is_done and other_state; } 

trebamo apstrahirati lokalnu varijablu tipa is_done i other_state , i trebamo apstrahirati while petlju, jer naš yield neće koristiti stog. Zato kreiramo abstrakciju i povezane klase:

 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(); } 

Glavni trik ovdje je premjestiti lokalne varijable u varijable klase i pretvoriti blokove regija u klase, što nam omogućuje “ponovno ući” u našu petlju nakon što primimo povratnu vrijednost.

Sada ostvariti našeg proizvođača

 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(); } } 

Slični trikovi mogu se izvesti i za druge osnovne strukture za kontrolu protoka. U idealnom slučaju, trebali biste kreirati biblioteku tih pomoćnih klasa, a zatim ih upotrijebiti za implementaciju tih jednostavnih sučelja, što će vam u konačnici pružiti semantiku lokalnih potprograma. Siguran sam da se sve što sam ovdje napisao može generalizirati i uvelike proširiti.

21
17 мая '10 в 9:02 2010-05-17 09:02 odgovor je dao luke 17. svi '10 u 9:02 2010-05-17 09:02

Predlažem da pogledate Kotlin coroutines na JVM . Međutim, to spada u drugu kategoriju. Nema manipulacije bajtnim kodom, a radi i na Androidu. Međutim, morate napisati svoje koroutine u Kotlinu. Dobra vijest je da je Kotlin dizajniran za interakciju s Java-om, tako da još uvijek možete koristiti sve svoje Java knjižnice i slobodno kombinirati Kotlin i Java kod u istom projektu, čak ih stavljajući rame uz rame u iste direktorije i pakete.

border=0

Ovaj vodič za kotlinx.coroutines sadrži mnogo više primjera, a prateći dizajn u dokumentu objašnjava sve motive, presedane i detalje implementacije.

4
05 апр. Odgovor dao Roman Elizarov Apr 05 2017-04-05 13:10 '17 u 13:10 2017-04-05 13:10

JA pravedan je došao preko ovaj pitanje i pravedan ištanje to spomenuti da mislim da je moguće provesti coroutines ili generatora na isti način kao C # ne. Međutim, ne koristim Javu, ali CIL ima ista ograničenja kao JVM.

C # operator je čisti jezični atribut i nije dio bajt koda CIL-a. C # prevodilac jednostavno stvara skrivenu privatnu klasu za svaku funkciju generatora. Ako koristite funkciju yield u funkciji, ona mora vratiti IEnumerator ili IEnumerable. Prevoditelj oblaže vaš kod u klasu statemachine.

C # prevodilac može koristiti neke "goto's" u generiranom kodu kako bi pojednostavio pretvorbu u statemachine. Ne znam mogućnost Java-byte-code i ako postoji nešto poput jednostavnog bezuvjetnog prijelaza, ali na razini "montaže" to je obično moguće.

Kao što je već spomenuto, ovu funkciju treba implementirati u kompajler. Budući da imam malo znanja o Javi i prevodiocu, ne mogu reći može li se kompajler mijenjati / proširiti, možda koristeći "predprocesor" ili nešto slično.

Osobno volim koroutine. Kao developer igara Unity, često ih koristim. Budući da igram Minecraft puno s ComputerCraftom, bio sam znatiželjan zašto se koroutine u Lui (LuaJ) implementiraju s potocima.

3
20 апр. Odgovor je dat Bunny83 20. travnja 2013-04-20 15:13 '13 u 15:13 2013-04-20 15:13

Imam Coroutine klasu koju koristim u Javi. Temelji se na nitima i koristi niti, ima prednost što omogućuje paralelni rad, što može biti prednost na strojevima s više jezgri. Stoga možete razmotriti pristup temeljen na nitima.

0
04 сент. Odgovor dao Howard Lovatt rujan 04. \ T 2012-09-04 13:33 '12 u 13:33 2012-09-04 13:33

Ovdje je još jedna mogućnost za java6 +

Izvođenje pythonic coroutine:

 import java.> 

Sada možete koristiti pythonic coroutine na ovaj način (na primjer, Fibonaccijev broj)

Verzija teme:

 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", ie, 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", ie, 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; } } 

Verzija sinkronizacije (bez streaminga):

 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; } } 

Izvršenje (obje verzije će raditi):

 // 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()); 
0
24 нояб. Odgovor je dao John Lee 24. studenog. 2017-11-24 11:47 '17 u 11:47 2017-11-24 11:47

Ostala pitanja o oznakama ili postavi pitanje