Od C++ jsem roky pryč, tak je možná můj komentář netypický, protože takhle se tam věci dělají, ale...
Neměl by být jeden interface future<> a případně několik implementací jako shared_future<> a deferred_future<> ? Jako klienta mě typicky nezajímá, jestli ještě někdo chce tu future<> využít, případně, jestli se pustí dřív či později.
Kromě toho mi přijde deferred_future<> trochu proti principu, kdy se očekává, že kód skutečně poběží non-blocking.
Jde převést future na shared_future na straně volajícího, tedy funkce vrací future ale já chci tam dat shared_future, tak mohu. Akorát je problém v tom, že to nejde udělat bez move operace, protože v tu dobu už někde existuje reference na již existující instanci. Takže to jde udělat jediné tak, že shared_future inicializuju lambdou která vrací future. Využívá se toho, ze shared_future si necha takto zinicializovat interni instanci future která se pak sdílí
Absolutně v tomhle systému nejde použít jakékoliv interface, protože v tu chvíli tam mám heap alokace
Deferred future nemusí nutně být blocking, on ten odložený výpočet může predstavovat jen jeho zahájení a dál jede jako future, Deferred future je dokonce potomkem future
Ta třída je takovým kompromisem kdy autor funkce si je vědom jak se bude obtížně pracovat s výsledkem, pokud by vracel future. Prostě ta informace je o tom, že volající ví, že po návratu z funkce ještě pořád neběží nic paralelně, že si může zvolit ten okamžik spuštění sám podle sebe. A pak je tam ten symetrický transfer, kdy lze takto přepínat korutiny - třeba právě asynchronní generátory
Chápu, že jde zabalit jedno do druhého. Na druhé straně je to značně netransparentní pro ostatní, bo nějaká úroveň musí najednou vědět, jestli někdo výš bude potenciálně potřebovat sdílet výsledek. Pokud bych už šel takovou zkratkou, tak bych udělal tu základní věc jako sdílenou.
Argument ohledně heap alokací mi přijde zcestný, neboť coroutines vyžadují heap alokace už z principu (celý stack je na heap, lambda je snad taky na heap (?) ).
Z toho deferred_future<> jsem teď zmatený - takže je to prakticky lambda, která teprve pustí výpočet a vlastně vrací future<>, akorát je to celé dáno do jednoho objektu? Vzhledem k tomu, že je záměrně netransparentní pro volajícího, možná by mělo smysl ty dvě věci rozdělit...
Korutina je heap. Ale jen jedna alokace na celou korutinu.
Callback je alokace, jedna alokace na každý callback. - mimochodem coro::function ktera se tam používa alokuje až když callback má víc než 24 bajtů (3x pointer), jinak se obejde bez alokace.
Jakmile by ten objekt alokoval jako callback, pak by to postrádalo smysl, protože by použití korutin nepřinášelo žádnou výhodu, snad kromě čitelnějšího kódu za cenu horší performance.
Tady je možnost právě umístit sdílená místa do frame korutiny, který je jedenkrát předalokované, ale já mohu v korutine mít cyklus a milionkrát vytvořit a zahodit future.
A v tom je celý point.
"Z toho deferred_future<> jsem teď zmatený - takže je to prakticky lambda, která teprve pustí výpočet a vlastně vrací future<>, akorát je to celé dáno do jednoho objektu? Vzhledem k tomu, že je záměrně netransparentní pro volajícího, možná by mělo smysl ty dvě věci rozdělit.."
V zásadě ano. Ovšem, kolikrát a v jakém formátu vracíš z funkce lambdu? Napiš mi nějaký prototyp deklarace funkce, co vrací lambdu.
Určitě použiješ std::function - heap allocation
(deferred_future - tedy použije coro::function, ten do 24 bajtů nealokuje, a handle korutiny má 8 bajtů, tam se vejde)
Na std::function neuděláš co_await, musíš z toho tedy nejprve vyextrahovat tu future.
na deferred_future uděláš co_await a všechno se to stane v tomto jednom kroku!
- zavolá lambdu (zapamatuje se vrácené handle, pokud je to korutina)
- předá jí promisu
- zaregistruje handle korutiny
- přepne do korutiny, která teď zahájí výpočet (pokud to není korutina, provedlo se to jako první bod)
jasně že by se to dalo rozepsat do víc kroků a navrhnout to takto jako separátní objekty. Budeš to takhle pak rozepisovat všude tam kde to použiješ? Nebo dáš přednost jednomu objektu? To je princip programování skládáš větší objekty z menších.
ještě k deferred_future
pokud napíšu
co_await foo()
tak pokud foo() je korutina nebo generátor, tak samotné zavolání funkce přepnutí nezpůsobí. Přepnutí způsobí až co_await, ten tedy vyžaduje aby foo() vrátila awaiter. tady právě deferred_future je ten awaiter který teprve v co_await zahájí operaci a dojde k přepnutí.
pokud by foo vracela pouze future, pak co_await nemůže nic přepínat, protože z jeho pohledu už výpočet běží. Takže pouze zaregistruje korutinu k probuzení na výsledku a vrátí řízení o level výš. z hlediska efektivity je lepší, když dvě korutiny se přepínaji na deferred_future než na future
abych zachoval určitý level abstrakce, tak i samotna future umi deferred režim a jak jsem psal v článku, smyslem deferred_future je hlavně zlepšení čitelnosti a umožnění move toho objektu, tedy pohodlnější zacházení (protože tam je move možný), pokud volaný 100 procent ví, že tam bude vždy korutina, muze tuhle informaci tímto způsobem předat volajícímu a tím mu rozvázát ruce,
Intenzivně se zabývám programováním zejména v jazyce C++. Vyvíjím vlastní knihovny, vzory, techniky, používám šablony, to vše proto, aby se mi usnadnil život při návrhu aplikací. Pracoval jsem jako programátor ve společnosti Seznam.cz. Nyní jsem se usadil v jednom startupu, kde vyvíjím serverové komponenty a informační systémy v C++
Přečteno 50 233×
Přečteno 23 535×
Přečteno 22 525×
Přečteno 20 434×
Přečteno 17 495×