Ich habe einige multithreaded C++-Code mit der folgenden Struktur:
do_thread_specific_work();
update_shared_variables();
//checkpoint A
do_thread_specific_work_not_modifying_shared_variables();
//checkpoint B
do_thread_specific_work_requiring_all_threads_have_updated_shared_variables();
Was auf Checkpoint B folgt, ist Arbeit, die begonnen werden könnte, wenn alle Threads nur Checkpoint A erreicht hätten, daher mein Begriff der "weichen Barriere".
Normalerweise bieten Multithreading-Bibliotheken nur "harte Barrieren", bei denen alle Threads einen bestimmten Punkt erreichen müssen, bevor einer von ihnen fortgesetzt werden kann. Offensichtlich könnte eine harte Barriere am Kontrollpunkt B verwendet werden.
Die Verwendung einer weichen Barriere kann zu einer besseren Ausführungszeit führen, insbesondere da die Arbeit zwischen den Kontrollpunkten A und B möglicherweise nicht zwischen den Threads ausgeglichen wird (d. h. ein langsamer Thread, der den Kontrollpunkt A, aber nicht B erreicht hat, könnte alle anderen dazu veranlassen, kurz vor dem Kontrollpunkt B an der Barriere zu warten).
Ich habe versucht, Dinge mit Atomics zu synchronisieren, und ich weiß mit 100%iger Sicherheit, dass dies NICHT garantiert funktioniert. Zum Beispiel mit openmp Syntax, vor dem parallelen Abschnitt beginnen mit:
shared_thread_counter = num_threads; //known at compile time
#pragma omp flush
Dann am Kontrollpunkt A:
#pragma omp atomic
shared_thread_counter--;
Dann am Kontrollpunkt B (mittels Polling):
#pragma omp flush
while (shared_thread_counter > 0) {
usleep(1); //can be removed, but better to limit memory bandwidth
#pragma omp flush
}
Ich habe einige Experimente entworfen, in denen ich eine atomare verwenden, um anzuzeigen, dass einige Operation, bevor es beendet ist. Das Experiment würde mit 2 Threads die meiste Zeit funktionieren, aber konsequent fehlschlagen, wenn ich viele Threads (wie 20 oder 30) haben. Ich vermute, dass dies an der Caching-Struktur moderner CPUs liegt. Selbst wenn ein Thread einen anderen Wert aktualisiert, bevor er das atomare Dekrement durchführt, ist nicht garantiert, dass er von einem anderen Thread in dieser Reihenfolge gelesen wird. Betrachten wir den Fall, dass der andere Wert ein Cache-Miss und das atomare Dekrement ein Cache-Treffer ist.
Also zurück zu meiner Frage, wie man diese "weiche Barriere" richtig implementiert? Gibt es eine eingebaute Funktion, die eine solche Funktionalität garantiert? Ich würde openmp bevorzugen, aber ich bin mit den meisten anderen gängigen Multithreading-Bibliotheken vertraut.
Als Workaround verwende ich derzeit eine harte Barriere am Checkpoint B und habe meinen Code so umstrukturiert, dass die Arbeit zwischen Checkpoint A und B automatisch zwischen den Threads ausgeglichen wird (was manchmal ziemlich schwierig war).
Danke für jeden Rat/Einblick :)