Threads Synchronisieren auch bei Lese-Zugriff?



  • Hi,

    das man Threads bei Schreib-Zugriffen via lock(), CRITICAL_SECTION (oder was auch immer fürn Konzept) Synchronisieren sollte ist verständlich, aber muss/sollte man auch bei Lese-Zugriffen Synchronisieren? (unabhängig davon ob man von der Programmlogik her sowieso Synchronisieren muss um Fehler oder sonstiges zu vermeiden) Und fals man Synchronisieren sollte/muss bei Lese-Zugriffe(n): Warum? ist es Architektur bedingt?



  • In der Regel schon, allerdings gibt es (wenige) Ausnahmen. (Als ein Beispiel könnte man evtl. erwähnen: http://en.wikipedia.org/wiki/Double-checked_locking)
    Eine Linkliste mit Zeigern kann man glaub ich (unter der Annahme, dass
    eine Zeiger-Zuweisung atomar ist) so implementieren, dass sie nicht gelockt werden muss bei Lesezugriffen.
    Leider kann man das denke ich nicht pauschalisieren.

    Häufig wird bei der Modifikation irgendwelcher Daten die interne Struktur
    verändert oder Speicherbereiche verlagert bzw. freigegeben.

    Ohne ein Lock kann dein Lesezugriff sich innerhalb deiner Datenstruktur
    einen Speicherbereich holen, der z. B. durch einen schreibenden
    Zugriff freigegeben wird und du hast eine Referenz, die ins Nirvana zeigt.
    Sogar während des Suchens in der Datenstruktur können dir
    durch den schreibenden Prozess Bereiche unterm Hintern weggezogen werden.

    EDIT: Streng genommen kannst du bei allen Operationen, die nicht atomar sind
    Probleme kriegen



  • Den Zugriff auf Konstanten oder so muss man z.B. nicht synchronisieren. Sobald die Daten jedoch verändert werden können, von woher auch immer, muss du das ganze synchronisieren.



  • Sobald ein Thread schreibt musst du synchronisieren, nur lesen alleine ist kein Problem.



  • 314159265358979 schrieb:

    Sobald ein Thread schreibt musst du synchronisieren, nur lesen alleine ist kein Problem.

    Wie kommst du denn darauf? Wenn jemand anderes dir die Daten unter den Fingern wegzieht, während du sie liest, stehst du vor einem Problem.



  • Wie meinen?



  • Thread 1: (nur lesender Zugriff)

    vector<data>::iterator pos = find(mydata.begin(),mydata.end(),testdata);
    ... // mach etwas mit *pos
    

    Thread 2: (schreibend)

    lock();
    mydata.push_back(new_value);
    unlock()
    

    push_back() kann den kompletten Speicher des vector's umkopieren - mit dem Ergebnis, daß der Iterator pos aus Thread 1 plötzlich ins Nirvana verweist.



  • Ich sagte, wenn es keinen schreibenden Thread gibt, gibts auch keine Probleme. Wenn einer schreibt muss gesynced werden, ist doch logisch.



  • Und an welcher Stelle entscheidest du, ob jetzt ein Thread schreibt? Wenn du das erst in dem Augenblick machst, wo wirklich ein Thread schreiben will, ist es zu spät. Sowas muß schon beim Design klar sein - prinzipiell kannst du bei jeder Datenstruktur, die erändert werden kann, davon ausgehen, daß auch ein Thread schreiben wird.



  • Sobald ein Thread irgendwas ändert, ist er ein schreibender Thread.



  • @CStoll & 314159265358979:
    Ich glaube ihr redet aneinander vorbei. (Ich glaube) 314159265358979 wollte sagen,
    dass gleichzeitiges Lesen kein Problem darstellt und ein Lese-Vorgang
    solange nicht gesynct werden muss wie ein paralleler Schreibzugriff
    ausgeschlossen wird, weil z. B. eine Datenstruktur nur einmal initialisiert wird
    oder ein Verändern wegen der Programmlogik ausgeschlossen werden kann.

    314159265358979 schrieb:

    Wenn einer schreibt muss gesynced werden, ist doch logisch.

    Ich bin mir nicht sicher, ob das für den Ersteller des Themas ganz trivial war 😉



  • 314159265358979 schrieb:

    Sobald ein Thread irgendwas ändert, ist er ein schreibender Thread.

    Und woher weiß ein lesender Thread jetzt, ob irgendwann während seiner Arbeit ein anderer Thread kommen und schreiben wird? Dein Computer kann nicht in die Zukunft sehen, deshalb muß er vom schlimmsten Fall ausgehen - wenn es möglich ist, die Datenstruktur zu ändern, wird auch jemand das aus einem anderen Thread heraus tun.

    (wer Angst hat, daß sich auf diese Weise zwei lesende Threads gegenseitig blockieren könnten, obwohl es unnötig ist, muß ein komplizierteres Synchronisationsverfahren verwenden - aber ohne Synchronisation kommst du nur aus, wenn du weißt, daß niemand jemals deine Daten ändern kann)

    Edit @Xpille: Danke für den Vermittlungsversuch.
    Daß man konstante Datenstrukturen nicht synchronisieren muß, war mir klar, deshalb habe ich auch von "jeder Datenstruktur, die verändert werden kann" gesprochen.



  • CStoll schrieb:

    Daß man konstante Datenstrukturen nicht synchronisieren muß, war mir klar, deshalb habe ich auch von "jeder Datenstruktur, die verändert werden kann" gesprochen.

    Daran hab ich zu keinem Zeitpunkt gezweifelt 😉

    Die zitierte Passage hab ich wohl überlesen 🙄



  • CStoll schrieb:

    Und woher weiß ein lesender Thread jetzt, ob irgendwann während seiner Arbeit ein anderer Thread kommen und schreiben wird? Dein Computer kann nicht in die Zukunft sehen, deshalb muß er vom schlimmsten Fall ausgehen - wenn es möglich ist, die Datenstruktur zu ändern, wird auch jemand das aus einem anderen Thread heraus tun.

    Das weiß der Thread nicht - das muss der Programmierer schon selbst wissen 😉



  • Gut, dann muß halt der Programmierer in die Zukunft sehen können - bzw. sehr genau wissen, wann und von wo aus die Daten geschrieben werden könnten.
    (Beispiel: Wenn du die Grundeinstellungen einmal zusammenbaust, bevor du deine Arbeits-Threads startest (die dann grundsätzlich nur lesend zugreifen), brauchst du keine Synchronisation. Wenn du irgendwann auf die Idee kommst, diese Einstellungen im laufenden Betrieb zu ändern, mußt du dafür sorgen, daß der Options-Bearbeiten-Thread nicht mit den Arbeits-Threads kollidiert)



  • Richtig.



  • Ein kleiner Denkanstoß:

    bei java ist es so, dass man bei änderbaren sachen selbst wenn es sich nur um einen integer handelt man beim lesen auch synchronisieren muss (in form von volatile, synchronized (eigentlich das gleich wie lock) oder lock().

    Der Grund ist, dass bei java wenn man keine der 3 varianten verwendet kein memory-refresh durchgeführt wird, und änderungen möglicherweise nicht sichtbar werden.

    Gibt es diese Problematik hier auch, oder ist das irgendwie anders gelöst? Z.B. könnten automatisch immer memory-flushs und memory-refreshs durchgeführt werden, was aber wohl zu performanceeinbußen führen würde, außer wenn der compiler irgendwie herausfinden kann, welche daten gemeinsam verwendet werden und welche nicht...



  • "Don't pay for what you don't use" lautet das Prinzip im wunderbaren C++ Land 😃



  • Gast1337 schrieb:

    bei java ist es so, dass man bei änderbaren sachen selbst wenn es sich nur um einen integer handelt man beim lesen auch synchronisieren muss (in form von volatile, synchronized (eigentlich das gleich wie lock) oder lock().

    Der Grund ist, dass bei java wenn man keine der 3 varianten verwendet kein memory-refresh durchgeführt wird, und änderungen möglicherweise nicht sichtbar werden.

    Die Speicher-Sichtbarkeit spielt nur dann eine Rolle, wenn etwas irgendwann irgendwo geändert wird. Wenn es immer schon so war, und immer so bleibt, dann braucht man auch in Java nichts spezielles zu machen damit Lesen OK ist.

    Da Threads starten und joinen implizite Full-Barriers sind, ist das auch kein rein theoretischer Fall.
    z.B. wenn du eine Datenstruktur anfüllst, und dann 10 Threads startest die die Daten darin lesen (und nur lesen), dann musst du nichts synchronisieren. Vorausgesetzt die Datenstruktur wird auch sonst nirgends geändert - zumindest nicht bevor alle Threads wieder gejoined wurden.

    Das ist mit "bei nur Lesen muss man nichts synchronisieren" gemeint.

    Gibt es diese Problematik hier auch, oder ist das irgendwie anders gelöst? Z.B. könnten automatisch immer memory-flushs und memory-refreshs durchgeführt werden, was aber wohl zu performanceeinbußen führen würde, außer wenn der compiler irgendwie herausfinden kann, welche daten gemeinsam verwendet werden und welche nicht...

    C++ garantiert was das angeht noch viel weniger als Java. z.B. ist das Lesen einer "volatile" Variable in C++ kein "load_acquire" und das Schreiben kein "store_release". (In Java dagegen schon).

    Also ja: die Problematik gibt es in C++ auch. Allerdings gilt auch in C++ das "bei nur Lesen muss man nichts synchronisieren" so wie ich es oben beschrieben habe.



  • Ok, das mit dem "nur Lese-Zugriff" hatte ich dann falsch verstanden.

    "load_acquire" und "store_release" sagt mir leider nichts, aber ich werds gleich mal googlen.

    Aber nochmal zu den garantien bei C++:
    Ich hab gelesen, dass C++ sequentielle Konsistenz garantiert.

    The C++0x draft ensures sequential consistency for programs that do not explicitly specify weaker memory ordering constraints, which avoids data races (concurrent non-read-only accesses to the same memory location).

    Quelle: http://en.wikipedia.org/wiki/C%2B%2B0x#Multitasking_memory_model
    Das bedeutet, so wie ich das verstehe , dass es niemals sichbarkeitsprobleme geben kann, also das bei jedem schreibzugriff, der von einem anderen thread gelesen werden könnte ein memory flush ausgeführt wird und bei jedem lesezugriff sofern eine änderung durch einen anderen thread möglich war ein memory refresh ausgeführt wird.
    Natürlich heißt das nicht, dass man keine locks mehr braucht.
    Das wären dann wesentlich stärkere garantieren als bei java, das wäre ja so, als wenn wirklich JEDE variable volatile wäre.

    "Don't pay for what you don't use" lautet das Prinzip im wunderbaren C++ Land

    Dann würde dieses zitat auch etwas hinfällig werden...
    Ich kann das auch nicht wirklich nachvollziehen, ich hatte es sonst immer so wahrgenommen, dass C(++) meistens so gebaut ist, dass hohe geschwindgkeit erreicht wird und das bei java eher sekundär ist, was hierbei dann ja genau umgedreht ist...

    Habe ich da was falsch verstanden?
    Bitte kommentieren 🙂


Anmelden zum Antworten