2010-07-26 6 views
7

Ich habe kleine, aber häufig verwendete Funktionsobjekte. Jeder Thread erhält eine eigene Kopie. Alles ist statisch zugeordnet. Kopien teilen keine globalen oder statischen Daten. Muss ich diese Objekte vor falscher Freigabe schützen?False sharing und stack Variablen

Vielen Dank. EDIT: Hier ist ein Spielzeug-Programm, das Boost.Threads verwendet. Kann eine falsche Freigabe für das Feld Daten auftreten?

#include <boost/thread/thread.hpp> 

struct Work { 
    void operator()() { 
     ++data; 
    } 

    int data; 
}; 

int main() { 
    boost::thread_group threads; 
    for (int i = 0; i < 10; ++i) 
     threads.create_thread(Work()); 
    threads.join_all(); 
} 
+0

Code würde besser funktionieren. Wenn Ihre Funktionsobjekte statische Daten haben, dann teilen alle Threads diese Daten. – GManNickG

+0

Denken Sie, dass Sie genau sagen müssen, was Sie mit "jeder Thread erhält seine eigene Kopie" und "statisch zugewiesen" meinen. Verwenden Threads die jeweils andere Kopie? – Elemental

+0

@Elemental: Einige Compiler können lokalen TLS-Thread-Speicher verwenden. Dies bedeutet, dass Sie statische und Thread-sicher zuweisen können, obwohl dies langsam ist. – Puppy

Antwort

6

False Freigabe zwischen Threads ist, wenn 2 oder mehr Threads die gleiche Cache-Zeile verwenden.

z. :

struct Work { 
    Work(int& d) : data(d) {} 
    void operator()() { 
     ++data; 
    } 

    int& data; 
}; 

int main() { 
    int false_sharing[10] = { 0 }; 
    boost::thread_group threads; 
    for (int i = 0; i < 10; ++i) 
     threads.create_thread(Work(false_sharing[i])); 
    threads.join_all(); 

    int no_false_sharing[10 * CACHELINE_SIZE_INTS] = { 0 }; 
    for (int i = 0; i < 10; ++i) 
     threads.create_thread(Work(no_false_sharing[i * CACHELINE_SIZE_INTS])); 
    threads.join_all(); 
} 

Die Threads in dem ersten Block leiden falsches Teilen. Die Threads im zweiten Block nicht (Danke an CACHELINE_SIZE).

Daten auf dem Stapel sind immer "weit" von anderen Threads entfernt. (Z. B. unter Windows, mindestens ein paar Seiten).

Mit Ihrer Definition eines Funktionsobjekts kann eine falsche Freigabe angezeigt werden, da die Instanzen Work auf dem Heap erstellt werden und dieser Heapspeicher innerhalb des Threads verwendet wird.

Dies kann dazu führen, dass mehrere Work Instanzen nebeneinander liegen und somit die gemeinsame Nutzung von Cache-Zeilen verursachen können.

Aber ... Ihre Stichprobe macht keinen Sinn, weil Daten nie außerhalb berührt werden und so falsches Teilen unnötig herbeigeführt wird.

Der einfachste Weg, um solche Probleme zu vermeiden, besteht darin, Ihre 'gemeinsamen' Daten lokal auf dem Stapel zu kopieren und dann an der Stapelkopie zu arbeiten. Wenn Ihre Arbeit abgeschlossen ist, kopieren Sie sie zurück in die Ausgabe var.

Z. B:

struct Work { 
    Work(int& d) : data(d) {} 
    void operator()() 
    { 
     int tmp = data; 
     for(int i = 0; i < lengthy_op; ++i) 
      ++tmp; 
     data = tmp; 
    } 

    int& data; 
}; 

Dies verhindert, dass alle Probleme mit Teilen.

+0

Wollen Sie sagen, dass Daten durch falsches Teilen beeinträchtigt werden können? In meinem Fall hilft das Kopieren in den Stack der Funktion nicht, da die Funktion selbst häufig aufgerufen werden muss und Daten nur einmal pro Aufruf verwendet werden. – user401947

+0

Wenn die Funktion sehr häufig aufgerufen werden muss, ist es nicht sinnvoll, jedes Mal einen Thread zu erstellen. Entweder arbeitest du viel in einem neuen Thread oder du verbrennst nur Zyklen für die Thread-Erstellung/-Zerstörung. Und im letzteren Fall überschatten Sie die Kosten für falsches Teilen durch die enormen Kosten der Threads. – Christopher

+0

Trotzdem. Wenn Sie für Ihre Operation keine Daten auf den Stack kopieren können, machen Sie "Arbeit" groß genug, um mindestens CACHLINE_SIZE lang zu sein. Sie verlieren ein paar Bytes, aber Sie können wirklich sicher sein, dass Sie nie Probleme mit der falschen Freigabe haben. – Christopher

0

Ich fühle mich nicht ganz sicher mit den Details, aber hier ist mein nehmen:

(1) Ihr vereinfachtes Beispiel gebrochen wird, da boost create_thread eine Referenz erwartet, übergeben Sie eine temporäre.

(2) Wenn Sie vector<Work> mit einem Element für jeden Thread verwenden oder sie nacheinander im Speicher haben, wird die falsche Freigabe auftreten.

+0

(1) Nein, es ist nicht kaputt. create_thread akzeptiert sein Argument nach Wert. Überprüfen Sie die Erklärung, wenn Sie mir nicht glauben. (2) Ich habe klargestellt, dass jeder Thread seine eigene Kopie erhält. Überprüfen Sie den Code. Das Funktionsobjekt wird als Wert übergeben. – user401947

+0

Es ist. Arbeit wird nicht in den Zielstapel kopiert. Es wird im Zusammenhang mit create_thread "newed" und nur ein (shared-) Pointer wird auf den Ziel-Stack übertragen. Dort werden die Daten nur durch einen Zeiger referenziert. (Ich habe das getestet, indem ich die thread_id dem data member zugewiesen habe und dann den Wert im Operator() -Aufruf betrachte.) – Christopher

+0

(1) wir sprechen über 'thread * create_thread (const boost :: function0 & threadfunc); '? Das habe ich gefunden, als ich versucht habe, die Referenz zu überprüfen – peterchen

2

Ich habe ein gutes Stück Forschung gemacht und es scheint, dass es keine Silberkugellösung zu falschem Teilen gibt. Hier ist, was ich (dank Christopher) habe: 1) Pad Ihre Daten von beiden Seiten mit unbenutzten oder weniger häufig verwendeten Sachen. 2) Kopieren Sie Ihre Daten in den Stapel und kopieren Sie sie zurück, nachdem alle harte Arbeit erledigt ist. 3) Verwenden Sie die Cache-Aligned-Speicherzuweisung.