Wird das Ändern eines Zeigers in C als atomare Aktion betrachtet?

Lesezeit: 6 Minuten

Benutzer-Avatar
Android

Wenn ich ein Multithread-Programm habe, das einen Cache-Speicher als Referenz liest. Kann ich diesen Zeiger durch den Hauptthread ändern, ohne zu riskieren, dass einer der anderen Threads unerwartete Werte liest.

Wie ich es sehe, werden die anderen Threads, wenn die Änderung atomar ist, entweder den älteren Wert oder den neueren Wert lesen; niemals zufälliger Speicher (oder Nullzeiger), richtig?

Ich bin mir bewusst, dass ich wahrscheinlich sowieso Synchronisierungsmethoden verwenden sollte, aber ich bin trotzdem neugierig.

Sind Zeigeränderungen atomar?

Update: Meine Plattform ist 64-Bit-Linux (2.6.29), obwohl ich auch gerne eine plattformübergreifende Antwort hätte 🙂

Benutzer-Avatar
Michael

Wie andere bereits erwähnt haben, gibt es in der C-Sprache nichts, was dies garantiert, und es hängt von Ihrer Plattform ab.

Auf den meisten modernen Desktop-Plattformen ist das Lesen/Schreiben an einem wortgroßen, ausgerichteten Ort atomar. Aber das löst Ihr Problem nicht wirklich, da Prozessor und Compiler Lese- und Schreibvorgänge neu ordnen.

Beispielsweise ist der folgende Code fehlerhaft:

Thema A:

DoWork();
workDone = 1;

Thema B:

while(workDone != 0);

ReceiveResultsOfWork();

Obwohl das Schreiben an workDone atomar ist, gibt es auf vielen Systemen keine Garantie durch den Prozessor, dass der Schreibvorgang erfolgt workDone wird für andere Prozessoren sichtbar sein, bevor Schreibvorgänge über durchgeführt werden DoWork() sind sichtbar. Es kann dem Compiler auch freistehen, den Schreibvorgang neu zu ordnen workDone bis vor dem Aufruf an DoWork(). In beiden Fällen, ReceiveResultsOfWork() könnte anfangen, an unvollständigen Daten zu arbeiten.

Abhängig von Ihrer Plattform müssen Sie möglicherweise Speicherzäune usw. einfügen, um eine ordnungsgemäße Reihenfolge sicherzustellen. Dies kann sehr schwierig sein, um es richtig zu machen.

Oder verwenden Sie einfach Schlösser. Viel einfacher, viel einfacher als korrekt zu überprüfen und in den meisten Fällen mehr als performant genug.

  • Der Compiler kann diese Neuordnung vornehmen, wenn er nachweisen kann, dass DoWork nicht auf definierte Weise auf workDone zugreift. Dies kann tatsächlich passieren, wenn DoWork klein genug ist und sich in derselben Übersetzungseinheit befindet und der Compiler beschließt, es einzubetten.

    – Derobert

    18. Mai 2009 um 19:20 Uhr

  • Obwohl es sich um ein C++-Schlüsselwort handelt, berücksichtigen die meisten C-Compiler den volatile-Modifikator, der den Compiler daran hindern würde, Schreibvorgänge in die Variable neu zu ordnen, und fast garantieren würde, dass der Speicher durch den Cache geschrieben wird. Ich sage fast, weil in C nichts garantiert ist, aber es ist die Konvention jedes großen C-Compilers.

    – Jeff Mc

    18. Mai 2009 um 19:33 Uhr

  • Ich glaube nicht, dass volatile allein eine Neuordnung verhindern kann, es zwingt den Compiler lediglich, beim Lesen neu abzurufen und das Wegschreiben nicht zu optimieren. Wenn gezeigt werden könnte, dass DoWork() workDone() nicht ändert und auf keine anderen flüchtigen Elemente zugreift, könnte der Compiler immer noch frei sein, workDone = 1 neu zu ordnen, selbst wenn es flüchtig wäre. Natürlich können Compiler dem Schlüsselwort volatile mehr Bedeutung hinzufügen, und ich habe von einigen Plattformen gehört, die volatile erweitern, um eine Bedeutung für die Neuordnung zu haben. Aber ich glaube nicht, dass der Standard dies garantiert.

    – Michael

    18. Mai 2009 um 19:37 Uhr

  • @Don: Speicherzäune sind nicht darauf ausgelegt, eine Neuordnung im Compiler zu verhindern, sie sollen eine Neuordnung in der CPU verhindern.

    – Ben Voigt

    29. Juni 2010 um 3:53 Uhr

  • @Don: Der Compiler muss erhalten bleiben scheinbare Ordnung in einer Singlethread-App ohne Signalhandler (Signalhandler sind möglicherweise kniffliger als Threads …). Sobald Sie Threads haben, müssen Sie in Ihre Threading-Bibliothek schauen; siehe pthread_barrier_* und ähnliches.

    – tc.

    13. August 2010 um 13:41 Uhr

Benutzer-Avatar
Dan Breslau

Die C-Sprache sagt nichts darüber aus, ob Operationen atomar sind. Ich habe an Mikrocontrollern mit 8-Bit-Bussen und 16-Bit-Zeigern gearbeitet; jede Zeigeroperation auf diesen Systemen wäre potentiell nicht-atomar. Ich glaube, ich erinnere mich, dass Intel 386 (von denen einige 16-Bit-Busse hatten) ähnliche Bedenken aufwarf. Ebenso kann ich mir Systeme mit 64-Bit-CPUs, aber 32-Bit-Datenbussen vorstellen, die dann ähnliche Bedenken hinsichtlich nicht-atomarer Zeigeroperationen mit sich bringen könnten. (Ich habe nicht überprüft, ob solche Systeme tatsächlich existieren.)

EDIT: Michaels Antwort ist sehr lesenswert. Die Busgröße gegenüber der Zeigergröße ist kaum die einzige Überlegung in Bezug auf die Atomarität; es war einfach das erste Gegenbeispiel, das mir einfiel.

  • @ojblass: Solche Dinge sind in älteren Architekturen passiert; sie können sogar mit neueren passieren. (Siehe meinen aktualisierten Kommentar.)

    – Dan Breslau

    18. Mai 2009 um 18:28 Uhr

  • @ojblass, wenn ich mit C/C++ arbeite, vertrete ich gerne die Haltung, dass alles möglich ist, bis die Standard- und Compiler-Dokumentation sagt, dass dies nicht der Fall ist. Nur so kann man sicher sein 🙂

    – JaredPar

    18. Mai 2009 um 19:11 Uhr

  • Das Lesen/Schreiben des Zeigers ist aus Sicht des Busses möglicherweise nicht atomar, aber wenn es sich um eine einzelne Anweisung handelt, ist es aus Sicht der CPU wahrscheinlich atomar. Seltsame Architekturen machen wahrscheinlich keine Art von Multiprocessing, also ist es so normalerweise fein. Normalerweise. Daumen drücken.

    – tc.

    13. August 2010 um 13:48 Uhr

  • @tc: Das Verhalten einzelner Anweisungen ist immer noch plattformspezifisch. Ich weiß, es gab etwas CPUs (obwohl ich mich nicht erinnere, welche), in denen Interrupts mitten in einer Anweisung verarbeitet werden konnten. Alle Wetten sind ungültig, wenn das passiert.

    – Dan Breslau

    15. August 2010 um 20:32 Uhr

Du hast keine Plattform erwähnt. Also ich denke, eine etwas genauere Frage wäre

Sind Zeigeränderungen garantiert atomar?

Die Unterscheidung ist notwendig, da sich verschiedene C/C++-Implementierungen in diesem Verhalten unterscheiden können. Es ist möglich, dass eine bestimmte Plattform atomare Zuweisungen garantiert und dennoch innerhalb des Standards bleibt.

Ob dies in C/C++ allgemein garantiert ist, lautet die Antwort Nein. Der C-Standard gibt keine solchen Garantien. Der einzige Weg, um zu garantieren, dass eine Zeigerzuweisung atomar ist, besteht darin, einen plattformspezifischen Mechanismus zu verwenden, um die Atomarität der Zuweisung zu garantieren. Beispielsweise bieten die Interlocked-Methoden in Win32 diese Garantie.

Auf welcher Plattform arbeitest du?

Die Ausweichantwort lautet, dass die C-Spezifikation keine atomare Zeigerzuweisung erfordert, sodass Sie sich nicht darauf verlassen können, dass sie atomar ist.

Die eigentliche Antwort wäre, dass es wahrscheinlich von Ihrer Plattform, Ihrem Compiler und möglicherweise der Ausrichtung der Sterne an dem Tag abhängt, an dem Sie das Programm geschrieben haben.

Die ‘normale’ Zeigermodifikation ist nicht garantiert atomar.

überprüfen Sie ‘Compare and Swap’ (CAS) und andere atomare Operationen, kein C-Standard, aber die meisten Compiler haben einen gewissen Zugriff auf die Prozessorprimitive. im Fall von GNU gcc gibt es mehrere eingebaute Funktionen

Benutzer-Avatar
Clyde

Das einzige, was der Standard garantiert, ist der Typ sig_atomic_t.

Wie Sie aus den anderen Antworten gesehen haben, ist es wahrscheinlich in Ordnung, wenn Sie auf eine generische x86-Architektur abzielen, aber sehr riskant mit “Spezial”-Hardware.

Wenn Sie es unbedingt wissen wollen, können Sie sizeof(sig_atomic_t) mit sizeof(int*) vergleichen und sehen, was Ihr Zielsystem ist.

Benutzer-Avatar
Gemeinschaft

Es stellt sich heraus, dass es sich um eine ziemlich komplexe Frage handelt. Ich habe eine ähnliche Frage gestellt und alles gelesen, worauf ich hingewiesen wurde. Ich habe viel darüber gelernt, wie Caching in modernen Architekturen funktioniert, und nichts Definitives gefunden. Wie andere gesagt haben, könnten Sie in Schwierigkeiten geraten, wenn die Busbreite kleiner als die Bitbreite des Zeigers ist. Insbesondere dann, wenn die Daten über eine Cache-Line-Grenze fallen.

Eine umsichtige Architektur verwendet eine Sperre.

1382800cookie-checkWird das Ändern eines Zeigers in C als atomare Aktion betrachtet?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy