Boost::barrier Problem



  • Hi,

    also ich starte mehrere Boost threads (in meinem Hauptthread) und verwende eine barrier um die threads entsprechend zu stoppen/starten wenn notwendig (damit ich sie nicht stets neu starten muss. Verwende keinen threadpool).

    Das funktioniert soweit auch alles super. Nun die Einsatzsituation und das Problem an einem simplen Beispiel erläutert.

    Ich habe eine Klasse die 3 member funktionen hat:

    class Test
    {
    public:
    void prepare();
    void run();
    void finish();
    }
    

    Nun ruft der Hauptthread die funktion prepare() auf. Darin alloziiere ich z.b. Speicher (und mache andere kleine Vorberechnungen).

    Dann starten die threads und jeder thread ruft run() entsprechend auf (übergibt desweiteren thread informationen).

    Nachdem alle threads fertig sind wird finish() vom Hauptthread zum Aufräumen aufgerufen.

    Soweit so gut, funzt. Die run() funktion bearbeitet einen bestimmten Bereich der Daten je nach thread index (à la: thread 0 elemente 0-99, thread 1 100-199 etc). Funzt.

    Nun kommt die Barrier ins Spiel. Eine run() funktion könnte ca so ausschauen:

    void Test::run(int idx, boost::barrier* ptr_bar)
    {
      fill_array();
    
      ptr_bar->wait();
    
      calculate_algorithm();
    }
    

    Beispielhaft:
    fill_array() befüllt das array mit werten (den threadbezogenen Elementbereich).
    Danach soll der thread warten bis tatsächlich alle threads fill_array() fertig aufgerufen haben.
    Denn calculate_algorithm() benötigt Lesezugriff auf befüllte Daten auch außerhalb seines threadbezogenen Elementbereichs.

    Nun endlich das Problem:
    Wenn ich mein Programm (ist natürlich etwas komplexer als dieses einfache Beispiel, aber konsistent mit dem Konzept) nun durchlaufen lasse, dann sind meine Werte nach calculate_algorithm() inkonsistent.

    Sprich immer wenn ich das Programm neu durchlaufen lasse variiert die Summe des arrays, obwohl es eigentlich immer gleich sein sollte.

    Nun habe ich festgestellt, dass wenn ich das Programm single-threaded laufen lasse bzw. keine barrier verwende sondern stattdessen die threads enden lasse und ein 2tes mal neu starte um run() aufzurufen (erst fill_array() und dann calculate_algorithm() sozusagen in run) dann ist die Summe konsistent, sprich es wird die korrekte Arraysumme ausgegeben.

    //funktioniert
    void Test::run(int idx, boost::barrier* ptr_bar)
    {
       switch(call){
           case 0: fill_array(); break;
           case 1: calculate_algorithm(); break;
       }
    }
    

    Wenn ich das Programm debuge kann ich sehen dass tatsächlich alle threads immer schön warten bei der Barrier. Auch die threadbezogenen Elementbereiche überlappen nirgends, also alles thread-safe und keine race-conditions möglich.

    Auch wenn ich calculate_algorithm() in finish() single threaded aufrufe funktioniert es! Also ca so:

    //funktioniert
    void Test::run(int idx, boost::barrier* ptr_bar)
    {
       fill_array();
    }
    
    void Test::finish()
    {
       calculate_algorithm();
       print_sum();
    }
    

    Die Daten die calculate_algorithm() aus dem array ausliest scheinen also irgendwie inkosistent zu sein wenn ich eine Barrier verwende.

    Hat jemand eine Idee woran das liegen könnte?

    Vielen Dank schonmal
    Gruß



  • Ist nur eine vage Vermutung aber du kannst mal das hier ausprobieren:

    void Test::run(int idx, boost::barrier* ptr_bar)
    {
    	fill_array();
    
    	boost::atomic_thread_fence(boost::memory_order_release);
    	ptr_bar->wait();
    	boost::atomic_thread_fence(boost::memory_order_acquire);
    
    	calculate_algorithm();
    }
    

    Die Boost Doku schweigt sich auch aus ob derren Barrier auch den Speicher synchronisiert.



  • Hi,

    vielen Dank! Auf den ersten Blick scheint es was zu bringen! Super, es scheint also an der Speichersync zu liegen.

    Leider bringt es auch starke Leistungsminderung mit sich (zumindest bei kleinen Datensätzen spürbar). Werde das mal genauer prüfen und dann entscheiden ob es nicht doch schneller ist, threads entsprechend neu zu starten.



  • Hmm, okay, also ich hab mal die boost barrier angeschaut und sie verwendet eine mutex welche über ein unique_lock gelocked wird. Wenn der letzte Thread gelocked werden soll gibt eine condition_variable den threads bescheid zum unlocken.

    Meinem Verständnis nach sollte das doch für eine Speichersynchronisation reichen oder nicht?

    Ich würde gerne verstehen wo der Fehler genau liegt oder ob ich notgedrungen etwas in der Implementierung falsch gemacht habe (und einfach nicht sehe!).

    Vielen Dank



  • Hab ich das richtig verstanden dass jeder Thread einen eigenen diskreten Speicherbereich bearbeitet?
    Ah jetzt verstehe ich.



  • Hast du ein minimales, lauffähiges Beispielprogramm? Würde da gerne mal selbst etwas rumspielen.


Log in to reply