Threads in C++ ?



  • Hast Du mal ein Beispiel, wo diese Möglichkeit wirklich sinnvoll einsetzbar ist, ohne dass die Nachteile zum Tragen kommen, weil z.B. die zum Thread gemachte Funktion nicht dazu taugt, als Thread ausgelagert zu werden?
    Also ein nicht hypothetisches Beispiel mit Hand und Fuss, wo einem der Code nicht um die Ohren fliegt?


  • Administrator

    @Tachyon,

    Ein Codebeispiel ist eigentlich ziemlich irrelevant. Sehr wahrscheinlich, kann man mit QTThread, bzw. mit jeglichem Thread-Framework, die jeweiligen Möglichkeiten des anderen nachbauen. Es fragt sich halt nur wie?
    Boost.Thread ist da halt sehr elegant und erlaubt es einem unkompliziert und mit wenig Codezeilen alles nachzubauen. Wenn du uns das nicht glaubst, dann musst du das nicht, aber es wäre zu empfehlen, dass du es vielleicht mal ausprobierst.

    Ich meine ich kann dir schon ein x-beliebiges Beispiel hinklatschen:

    void a_function(); // <- Die ist z.B. aus irgendeinem Grund fix. Soll jetzt aber in einem Thread laufen
    
    int main
    {
        boost::thread t(&a_function); // <- Das ist alles!
        return 0;
    }
    

    Aber man könnte es durchaus auch so halten:

    void 1st_function(int); // wieder fix.
    void 2nd_function(float, int); // auch fix.
    
    // Und noch eine fixe Klasse, zum Beispiel aus einer fremden Bibliothek!
    class CClass
    {
        // Whatever
    public:
        void expensive_calculation();
    }
    
    int main()
    {
        boost::thread t1(boost::bind(&1st_function, 234)); // Thread 1
        boost::thread t2(boost::bind(&1st_function, 500)); // Thread 2
        boost::thread t3(boost::bind(&2nd_function, 0.1f, 450)); // Thread 3
    
        CClass MyClass;
    
        MyClass.expensive_calculation(); // Das geht mir zu lange, also anders.
        boost:thread t4(boost::bind(&CClass::expensive_calculation, &MyClass)); // Thread 4
    
        t1.join(); // oder auch nicht
        t2.join(); // oder auch nicht
        t3.join(); // oder auch nicht
        t4.join(); // Naja, hier müssen wir, da MyClass nicht zerstört werden darf.
    
        return 0;
    }
    

    Die joins hätte man auch sehr schön über thread_group lösen können, aber das ist wieder was anderes. Schau dir dazu die Library an.

    Und Synchronizationen sind auch ganz einfach:

    boost::mutex g_Mutex;
    typedef boost::lock_guard<boost::mutex> t_Sync;
    
    void anywhere()
    {
        {
            t_Sync Sync(g_Mutex);
    
            // Synchronisiert.
        }
    }
    

    Dann kannst du dir gerne noch die conditional variables oder barrier anschauen. Es ist kurz, einfach und schnell gehalten und deswegen auch extrem flexibel.

    Denk dir irgendeinen Code mit QTThread aus. Ich bin mir sicher, dass man es gleich oder besser lösen kann.

    Grüssli



  • Es geht IMO vor allem darum dass man nicht gezwungen wird Code zu schreiben den man eigentlich nicht braucht.
    Mit Vererbung muss ich immer eine Klasse basteln die von XYZ ableitet, auch wenn ich diese garnicht braucht.

    Ein weiteres Argument gegen Vererbung ist dass die Ownership problematisch wird sobald man den Thread als Objekt betrachtet, welches eine Memberfunktion ausführt.
    Wie das Ergebnis aussieht sieht man ja an der MFC Thread-Klasse - grausam.

    Was du als "objektive Argumente" anerkennst und was du als subjektiv abtust ist deine Sache, interessiert mich eigentlich kaum.

    Lies die Design Rationale von Boost.Thread wenns dich interessiert, ist ja schliesslich kein Geheimnis.



  • Das ist das, was ich mit "hypothetisches Beispiel" meinte. Du wirst in 99.9% aller Fälle nicht einfach so irgendwelche "fixen" Funktionen in einen Thread auslagern können, ohne diese vorher irgendwie anzupassen, zu wrappen, in eine Klasse zu packen oder was weiss ich.


  • Administrator

    Tachyon schrieb:

    Das ist das, was ich mit "hypothetisches Beispiel" meinte. Du wirst in 99.9% aller Fälle nicht einfach so irgendwelche "fixen" Funktionen in einen Thread auslagern können, ohne diese vorher irgendwie anzupassen, zu wrappen, in eine Klasse zu packen oder was weiss ich.

    Also aus eigener Erfahrung, mit Boost.Thread schon 😉

    Grüssli



  • Dravere schrieb:

    Tachyon schrieb:

    Das ist das, was ich mit "hypothetisches Beispiel" meinte. Du wirst in 99.9% aller Fälle nicht einfach so irgendwelche "fixen" Funktionen in einen Thread auslagern können, ohne diese vorher irgendwie anzupassen, zu wrappen, in eine Klasse zu packen oder was weiss ich.

    Also aus eigener Erfahrung, mit Boost.Thread schon 😉

    Grüssli

    Dann werde doch endlich mal konkret!
    PS: Ich will boost threads nicht schlechtmachen. Wenn es mir zur Verfügung steht benutze ich es selbst. Nur leider habe ich nicht immer die Wahl.
    Und aus der Erfahrung heraus, beide Anseätze benutzen zu müssen, kann ich, ausser etwas syntaktischem Zucker, keine objektiven Argumente für den einen oder den anderen Ansatz finden.
    Das Ownerschip-Problem bekommt man übrigens auch, wenn man irgendwelche Meberfunktionen irgendeiner Klasse in einen Boost Thread packt.

    Ich bin ja offen für gute Argumente. Aber sowas wie "wenn Du das nicht siehst...", "...ich finde..." und "...weil es meine Meinung ist..." sind für mich eben keine Argumente. Und nein, ich sehe es nicht. Vielleicht bin ich auch einfach zu dämlich. Aber stichhaltige Argumente würden mich eher überzeugen als hypothetischer Code und persönliche Beleidigungen.



  • vererbung ist die staerkste bindung die es gibt. deshalb: vererbung nur dann wenn es nicht anders geht.

    kleine gegenfrage: warum findest du den vererbungsansatz gut? du brauchst mehr code und den code den du schreibst ist nicht gut wiederverwendbar. ich sehe nur nachteile - nicht immer gravierende sachen, aber wo ist der vorteil? ich sehe keinen einzigen.

    wir koennen natuerlich so lange herumreden bis die vorteile von objekt basierten threads alle wegdiskutiert wurden - aber die zentrale frage ist: was bieten vererbungsbasierte threads?


  • Administrator

    Tachyon schrieb:

    Das Ownerschip-Problem bekommt man übrigens auch, wenn man irgendwelche Meberfunktionen irgendeiner Klasse in einen Boost Thread packt.

    Aber es ist nicht zwingend der Fall. Und man kann dem Thread Objekt auch eine Kopie übergeben und darauf dann die Memberfunktion aufrufen, dann besteht das Problem überhaupt gar nicht mehr.

    Konkretes Beispiel willst du, ok.

    Ich habe mal mit einer Bibliothek gearbeitet. Die hatte eine Http Klasse drin. Diese Klasse hat nichts anderes gemacht, als eine Datei aus dem Inet zu laden und auf der Festplatte zu speichern. Ich musst nur die richtigen Dateien in der richtigen Zeit runterladen und speichern. Alles danach hat mein Programm nicht interessiert. Es war halt eine Art von Log. Die Klasse kümmerte sich auch gleich um eine Fehlerdatei, fall es nicht geklappt hat usw. usf.
    Ich musste also nur Zielort auf der Festplatte und im Inet angeben, der Rest erledigte die Bibliothek.

    Das Problem war nun, dass die Klasse nicht Multithreaded war, aber eine Anfrage an einen Server zwischen 200-300ms benötigt hat und der Download hat dann nochmals Zeit benötigt.

    class CHttpDownload; // So in der Art hiess die Klasse.
    
    CHttpDownload Download("...url...", "...speicherpfad...");
    Download.start(); // -> brauchte teilweise echt lange.
    
    // Lösung:
    boost::thread t(boost::bind(&CHttpDownload::start, Download));
    
    // Und ich kann ungestört weitermachen.
    

    Aber hier wirst du natürlich sagen, dass das der Fall von diesem 0,1% ist. Aber was soll ich sagen, Boost.Thread erlaubt dies eben, die anderen nicht!
    Schon nur aus diesem Grund ist Boost.Thread flexibler.

    Grüssli



  • @Tachyon:
    Du meinst also dass ein gutes Argument schlecht wird bloss weil jmd. "wie ich finde" oder ähnliches davor/dahinter schreibt?

    Das Ownerschip-Problem bekommt man übrigens auch, wenn man irgendwelche Meberfunktionen irgendeiner Klasse in einen Boost Thread packt.

    Lässt sich in dem Fall aber ganz einfach lösen indem man anstelle eines rohen Zeigers einen shared_ptr bindet.

    Noch etwas: ich betrachte es schon als "objektiven Vorteil" wenn ich weniger Code schreiben muss. Mit dem Boost.Thread Ansatz um Threads zu starten muss ich oft weniger Code schreiben, auch wenn es nur ein paar Zeilen sind. Ist das jetzt objektiv genug? Oh, ne, warte, ich hab ja schon wieder "ich betrachte ... " geschrieben. Hier nochmal:
    VIEL CODE SCHLECHT
    WENIGER CODE GUT
    BOOST.THREAD -> WENIGER CODE
    BOOST.THREAD -> GUT

    Gut so?

    Und aus der Erfahrung heraus, beide Anseätze benutzen zu müssen, kann ich, ausser etwas syntaktischem Zucker, keine objektiven Argumente für den einen oder den anderen Ansatz finden.

    Dass mit beiden Ansätzen im Endeffekt dasselbe erreicht werden kann sollte auch klar sein. Da es möglich ist das eine Interface auf das jeweils andere abzubilden ist das auch irgendwie logisch. Trotzdem kann das eine Interface praktischer sein als das andere, oder halt einfach "intuitiver" oder was auch immer. Aber das ist ja wieder alles subjektiv und gilt nicht oder wie.

    Und was syntactic sugar anghet: alles das was es in C++ gibt und in C nicht ist nur syntactic sugar (ok, ausgenommen templates). Ändert das jetzt etwas daran dass C++ einfach praktischer ist?



  • Dravere schrieb:

    [...]

    Okay, so langsam sehe ich das ein. Ich bin halt manchmal etwas stur. 😃


Anmelden zum Antworten