designfrage: futures



  • Also, der Reihe nach (das wird dauern, hab' nicht mit so vielen Antworten gerechnet) ...

    Was ist denn der Zweck der Klasse? Kann sein, dass eine future-Klasse per se eine bestimmte Aufgabe hat, aber ich denke ein Großteil der Benutzer hier weiß erstmal nix damit anzufangen

    Eine future ist ein quasi eine Art Variable die erst "in der Zukunft" einen Wert bekommt.
    Verwenden tut man das dann meist in Zusammenhang mit Threads (muss aber nicht, man kann genauso ne future basteln die den Wert sofort bestimmt oder die ihn einfach in dem Thread ausrechnet der den Wert abfragt).
    Vielleicht helfen 2 kleine Beispiele dass du dir was vorstellen kannst:

    // beispiel 1:
    
    typedef boost::shared_ptr<image> image_ptr;
    
    class my_super_game
    {
    public:
    
    	static int const background_id = 1;
    	static int const menu_graphics_id = 2;
    
    	my_super_game()
    		:	m_thread_pool(1) // "pool" mit einen thread anlegen (nur einer, sonst würde sich load_image beim disk IO selbst ausbremsen)
    	{
    		// laden der bilder starten
    		m_images[background_id] = xyz::make_future(m_thread_pool.start(&my_super_game::load_image, boost::bind(L"bg.bmp"));
    		m_images[menu_graphics_id] = xyz::make_future(m_thread_pool.start(&my_super_game::load_image, boost::bind(L"menu.bmp"));
    	}
    
    	void draw_background()
    	{
    		image& bg = get_image(background_id); // bild holen
    		// ...
    	}
    
    	image& get_image(int id) const
    	{
    		return *(m_images[id].get_result()); // wartet bis das bild geladen wurde und gibt dann das ergebnis zurück
    	}
    
    	static image_ptr load_image(std::wstring const& name)
    	{
    		// ...
    	}
    
    private:
    	xyz::thread_pool m_thread_pool;
    	std::map<int, xyz::future<image_ptr> mutable m_images;
    };
    
    // beispiel 2:
    
    void foo(int a, int b, int c)
    {
    	// hoffentlich selbsterklärend:
    	xzy::future<int> x(xyz::start_thread(boost::bind(&lengthy_computation_1, a, b)));
    	xzy::future<int> y(xyz::start_thread(boost::bind(&lengthy_computation_2, b, c)));
    	return x() + y();
    }
    


  • Helium schrieb:

    'Ne Abfrage, ob ne Exception oder ein Ergebnis vorliegt wäre vielleicht manchmal ganz nett, aber IMO nicht unbedingt wichtig. Zumal man dann ja erst waren müsste, dann gucken ob ein wert vorliegt und dann den Wert abholen.

    Naja, wenn dann würde die "wurde mit exception beendet?" Funktion blockieren, anders macht das IMO keinen Sinn.
    Eine andere Möglichkeit wäre der "is_finished" bzw. "is_ready" Funktion nen int als Returnwert zu verpassen wo dann 0 = läuft noch, 1 = erfolgreich, -1 = exception. Ist aber nicht sehr selbsterklärend, von daher werde ich das wohl eher nicht machen.

    Ein "get" statt "get_result" würde mir in dem Fall reichen. Vielleicht aber auch der C++-typische operator*.

    Nö, also () UND * überlade ich sicher nicht. Und () wird fix überladen (einfach weil das Ding als Funktor verwendbar sein soll).



  • Das ist jetzt Absicht, dass man hier den asynchronen Aufruf noch nicht sieht, oder? EDIT: Achso, das ist get_task_handle oder? Das würde ich aber echt anders nennen. EDIT2: Ok. Der ()-Operator macht den Aufruf nehme ich an. Was macht get_task_handle? Und gibt es eine Möglichkeit, den Future auch direkt blockierend aufzurufen (will man manchmal auch)?

    Würdet ihr eine "is_ready" Funktion einbauen -- bzw. eine solche Funktion unter einem anderen Namen?

    Ja, habe ich schon ein paar mal brauchen können. Würde ich auf jeden Fall einbauen.



  • Artchi schrieb:

    empty() reicht meiner Meinung nach völlig. Weil empty auf deutsch "leer" und nicht "leeren" (to empty) heißt. Also zum leeren wäre vielleicht to_empty() sinnvoll? to_ als prefix habe ich aber selten gesehen. 😉

    Das liegt wohl einfach daran dass C++ eine imperative Programmiersprache ist, und der Imperativ von "to empty" ist nunmal "empty!" (und der von "to clear" eben "clear!") 😉



  • Optimizer schrieb:

    Das ist jetzt Absicht, dass man hier den asynchronen Aufruf noch nicht sieht, oder? EDIT: Achso, das ist get_task_handle oder? Das würde ich aber echt anders nennen.

    Der future selbst ist das im Prinzip egal wo der Wert herkommt, die soll nur ein common Interface sein für
    * threads
    * thread pools
    * lazy evaluation
    * stink normale "fertige" werte (wenn man mit was interfacen muss was ne future annimmt, aber keine braucht)
    * theoretisch sogar asynchrone IOs ohne eigenen thread
    Der asynchrone Aufruf ist das mit was man die future "anfüllt" bevor man sie verwenden kann.
    Und was würdest du anders nennen? Die "get_task" Funktion müsste man garnicht unbedingt haben, die ist nur dazu da dass man nen "task" den man reingesteckt hat auch wieder "auslesen" kann, falls man das mal braucht.
    Und zum Erzeugen dieser "task handles" sind dann Klassen wie thread_starter, thread_pool etc. zuständig, bzw. wer auch immer das Interface "abstract_task_handle" implementieren will.

    Würdet ihr eine "is_ready" Funktion einbauen -- bzw. eine solche Funktion unter einem anderen Namen?

    Ja, habe ich schon ein paar mal brauchen können. Würde ich auf jeden Fall einbauen.

    Jop, werde ich dann wohl machen.



  • thordk schrieb:

    vom überladen des () operators rate ich übrigens ab. hab das ne zeitlang auch mal gemacht und es führte schlußendlich dazu, dass der code unglaublich unleserlich wurde. man mag sich erst denken, dass so ein operator ne tolle sache ist, weil er zeichen spart, aber im endeffekt sorgt er mehr für verwirrung.

    Den () will ich auf jeden Fall überladen, damit das Ding ein Funktor ist. Einige Libraries (wie z.B. die STL selbst oder auch boost) können mit Funktoren schön umgehen, dadurch werden einige Dinge recht elegant möglich die sonst irgendwelche blöden Helferklassen oder zusätzliche Aufrufe von boost::bind benötigen würden.
    Auf jeden Fall wird's aber zusätzlich eine "namentliche" Methode ala "get_result" geben.



  • Ich hab deinen Future zu sehr als Funktor mit Future-Funktion gesehen. Jetzt ist es klarer, es soll nur das Ergebnis repräsentieren, ok.



  • Danke für die ausführliche Erklärung hustbaer! Diese futures sind ja eine abgefahrene Sache an die mein bescheidener Verstand gar nicht zu denken gewagt hätte 🤡
    Das Interface kommt jedenfalls schonmal in meine Schnipsel-Sammlung, wenn du nichts dagegen hast. Würde es dir etwas ausmachen, den kompletten Code zu posten, wenn die Klasse fertig ist?



  • @Badestrand:
    Es geht um etwas mehr als bloss eine Klasse, das wird eine ganze Threading Library 🙂
    Und es wird leider noch dauern (paar Monate schätze ich) bis die Sache fertig ist (Windows Version, andere OSe kommen später dran).

    Kommt aber dann auf jeden Fall auf Source-Forge, und ich werde sicher hier kurz posten.



  • p.S.: was das "is_" oder nicht "is_" angeht: nachdem die Meinungen hier ziemlich 50:50 verteilt sind (wenn ich richtig gezählt habe) werde ich einfach nach meiner persönlichen Vorliebe "is_" und "get_" etc. verwenden.

    (Was eigentlich irgendwie sowieso klar war, da die ganzen anderen Klassen in der Library auch eher "sprechend & lang zum schnell verstehen" als "schön kurz für kleine bildschirme" gehalten sind.)



  • hustbaer schrieb:

    Eine andere Möglichkeit wäre der "is_finished" bzw. "is_ready" Funktion nen int als Returnwert zu verpassen wo dann 0 = läuft noch, 1 = erfolgreich, -1 = exception. Ist aber nicht sehr selbsterklärend, von daher werde ich das wohl eher nicht machen.

    Wieso denn irgendwelche Zauberkonstanten? Wieso nicht eine Enumeration, die teil des futures ist, so dass man dann

    if (foo.is_ready() == future<...>::exception)
    

    schreibt.
    Wobei das auch doof ist.

    if (foo.is_ready())
    

    Soll ja auch Funktionieren.

    Dann müsste man sich da einen Tick mehr Mühe geben und irgendwas zurückliefern, das sich in if() verwenden lässt, aber auch mit bestimmten vorgefertigten Konstanten (successfull, ...) vergleichen lässt.



  • Ich finde die Future-Sachen auch toll, sie werden höchstwahrscheinlich in C++0x oder TR2 enthalten. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2276.html
    Bin sogar der Meinung, das Herb Sutter es mal als Sprachfeature (und nicht als Lib) vorgestellt hatte.



  • Als teil des Concur-Projekts



  • Helium schrieb:

    Wieso denn irgendwelche Zauberkonstanten? Wieso nicht eine Enumeration, die teil des futures ist, so dass man dann

    if (foo.is_ready() == future<...>::exception)
    

    schreibt.
    Wobei das auch doof ist.

    if (foo.is_ready())
    

    Soll ja auch Funktionieren.

    Vielleicht

    switch (foo.get_state()) {
    case future<...>::busy:
    case future<...>::ready:
    case future<...>::exception_occured:
    }
    

    und dazu

    inline bool is_ready() const { return get_state() == ready; }
    // etc.
    






  • Ach net, ist irgendwie schade, dass du keinen Nick mehr hast 😞



  • Badestrand schrieb:

    Ach net, ist irgendwie schade, dass du keinen Nick mehr hast 😞

    warum? es hat sich kaum was dadurch verändert.
    🙂



  • Wenn es nicht schon draufsteht hustbaer, kannst du dann der Zu-tun-Liste auch noch ultraleichtgewichtige Threads hinzufügen? Ich möchte mehr erlang-Stil in C++, hab aber zz keine anständige „Umgebung“ um sowas zu basteln 😉


Anmelden zum Antworten