2014-05-17 10 views
5

Was sind einige gute Praktiken, um Race Conditions in Go zu verhindern?Golang: Rennbedingungen vermeiden

Die einzige, die ich mir vorstellen kann, ist nicht die gemeinsame Nutzung von Daten zwischen goroutines - die parent goroutine sendet eine tiefe Kopie eines Objekts und nicht das Objekt selbst, so dass die Kind goroutine nicht etwas mutieren kann. Dies würde mehr Heap-Speicher verbrauchen, aber die andere Alternative ist, Haskell zu lernen: P

Edit: auch, gibt es irgendein Szenario, in dem die oben beschriebene Methode noch in Race-Bedingungen laufen kann?

+1

Race-Bedingungen sind schwer zu debuggen, es ist auch schwierig zu meistern, sie zu vermeiden. Ich würde vorschlagen, über die hier beschriebenen Muster von Rob Pike http://vimeo.com/49718712 nachzudenken, während er bestimmte Konstrukte durchläuft, wie Kanäle und CSP-Semantiken eine Anwendung durch Design sicher machen, anstatt sich um alle Probleme zu kümmern, die mit Mutexen verbunden sind. Es tut mir leid, wenn dies Ihre Frage nicht beantwortet, aber ich hoffe, es öffnet mehr Türen zu neuen Ideen. – ymg

+0

Danke für den Link. Ja, Rennbedingungen sind schwer zu verhindern, aber noch schwieriger zu debuggen! Ich denke, eine unveränderliche Version der Sammlungen würde das Problem in gewissem Maße lindern, aber dann sind wir wieder im Hinterkopf der funktionalen Programmierung. – tldr

+0

Dort sind _andere unveränderlich-basierte/funktionale Sprachen (wie F #), aber es geht wirklich um Stil. Sie haben recht, wenn Sie mehr Speicher benötigen, aber richtiges Design kann einen Teil des Overheads eliminieren. Zum Beispiel können Write-On-Modifikationsarrays im 'n log n'-Raum (oder weniger für einige potentielle Fälle) im Durchschnitt anstatt in einem naiven' 2n' gemacht werden. Obwohl Sprach/Optimizer-Unterstützung einige verrückte Dinge für die speziell entwickelten Sprachen tun kann ... –

Antwort

13

Race Conditions können sicherlich auch bei ungeteilten Datenstrukturen noch bestehen. Beachten Sie Folgendes:

B asks A for the currentCount 
C asks A for the currentCount 
B sends A (newDataB, currentCount + 1) 
A stores newDataB at location currentCount+1 
C sends A (newDataC, currentCount + 1) 
A stores newDataC at currentCount + 1 (overwriting newDataB; race condition) 

Diese Race-Bedingung in einem privaten wandelbaren Zustand erfordert, aber keine änderbaren Datenstrukturen geteilt und nicht einmal wandelbaren Zustand in B oder C erfordert Es gibt nichts, B oder C ist tun können, um zu verhindern diese Wettlaufbedingung, ohne den Vertrag zu verstehen, den A anbietet.

Sogar Haskell kann diese Art von Race Conditions erleiden, sobald der Zustand in die Gleichung eintritt, und der Zustand ist sehr schwer vollständig aus einem realen System zu eliminieren. Schließlich möchten Sie, dass Ihr Programm mit der Realität interagiert und dass die Realität zustandsbehaftet ist.Wikipedia gibt a helpful race condition example in Haskell mit STM.

Ich stimme zu, dass gute unveränderliche Datenstrukturen die Dinge erleichtern könnten (Go hat sie nicht wirklich). Veränderliche Kopien tauschen ein Problem gegen ein anderes. Sie können die Daten eines anderen nicht versehentlich ändern. Auf der anderen Seite, können Sie denken, dass Sie die echte ändern, wenn Sie eigentlich nur eine Kopie ändern, was zu einer anderen Art von Fehler führt. Sie müssen den Vertrag auf jeden Fall verstehen.

Aber letztlich neigt Go die Geschichte von C auf Parallelität folgen: Sie einige Besitzregeln für Ihren Code bilden (wie @ tux21b Angebote) und stellen Sie sicher, dass Sie sie immer folgen, und wenn Sie es perfekt machen werde es alle arbeiten großartig, und wenn du jemals einen Fehler machst, dann ist es offensichtlich deine Schuld, nicht die Sprache.

(Versteh mich nicht falsch, Ich mag Go, eine ganze Menge wirklich Und es bietet einige nette Tools in die Parallelität leicht machen es einfach nicht viele Sprach-Tools nicht bieten korrekte zu helfen, machen Gleichzeitigkeit Das ist... Das heißt, die Antwort von tux21b bietet viele gute Ratschläge, und der Race Detector ist definitiv ein mächtiges Werkzeug, um die Rennbedingungen zu reduzieren.Es ist einfach kein Teil der Sprache, und es geht um Testen, nicht um Korrektheit, sie sind nicht die Die gleiche Sache.)

EDIT: Auf die Frage, warum unveränderliche Datenstrukturen die Dinge einfacher machen, ist dies die Erweiterung Ihres Ausgangspunkts: Erstellen eines Vertrags, wo mehrere Parteien nicht die gleichen Daten ändern eine Struktur. Wenn die Datenstruktur unveränderlich ist, dann kommt das kostenlos ...

Viele Sprachen verfügen über eine umfangreiche Sammlung unveränderlicher Sammlungen und Klassen. C++ ermöglicht Ihnen const einfach alles. Objective-C verfügt über unveränderbare Auflistungen mit änderbaren Unterklassen (wodurch eine andere Gruppe von Mustern erstellt wird als const). Scala hat verschiedene veränderbare und unveränderbare Versionen vieler Sammlungsarten, und es ist allgemein üblich, ausschließlich die unveränderlichen Versionen zu verwenden. Die Angabe der Unveränderlichkeit in einer Methodensignatur ist ein wichtiger Hinweis auf den Vertrag.

Wenn Sie eine []byte an eine Goroutine übergeben, gibt es keine Möglichkeit, aus dem Code zu erfahren, ob die Goroutine beabsichtigt, die Schicht zu ändern, noch wann Sie die Schicht selbst modifizieren können. Dort tauchen Muster auf, aber sie sind wie C++ - Objektbesitz vor der Umzugs-Semantik; viele gute Ansätze, aber keine Möglichkeit zu wissen, welcher verwendet wird. Es ist eine kritische Sache, die jedes Programm richtig machen muss, aber die Sprache gibt Ihnen keine guten Werkzeuge, und es gibt kein universelles Muster, das von Entwicklern benutzt wird.

+1

Dies ist eine ausgezeichnete Antwort. Können Sie ein wenig darauf eingehen, wie unveränderliche Datenstrukturen die Dinge erleichtern? – tldr

6

Go erzwingt Speichersicherheit nicht statisch. Es gibt mehrere Möglichkeiten, das Problem selbst in großen Codebasen zu behandeln, aber alle erfordern Ihre Aufmerksamkeit.

  • können Sie Zeiger senden herum, aber ein gemeinsames Idiom ist die Übertragung des Eigentums zu signalisieren, durch einen Zeiger zu senden. Wenn Sie beispielsweise den Zeiger eines Objekts einer anderen Goroutine übergeben, berühren Sie sie nicht erneut, es sei denn, Sie erhalten das Objekt von dieser Goroutine (oder einer anderen Goroutine, wenn das Objekt mehrere Male durchlaufen wird) durch ein anderes Signal zurück.

  • Wenn Ihre Daten von vielen Benutzern gemeinsam genutzt werden und sich nicht häufig ändern, können Sie global einen Zeiger auf diese Daten freigeben und allen Benutzern das Lesen erlauben. Wenn eine Goroutine diese ändern möchte, muss sie dem Copy-on-Write-Idiom folgen, d. H. Das Objekt kopieren, die Daten mutieren und versuchen, den Zeiger auf das neue Objekt zu setzen, indem etwas wie atomic.CompareAndSwap verwendet wird.

  • Die Verwendung eines Mutex (oder eines RWMutex, wenn Sie viele gleichzeitige Leser auf einmal zulassen möchten) ist nicht so schlimm. Sicher, ein Mutex ist kein Wundermittel und eignet sich oft nicht für die Synchronisierung (und wird in vielen Sprachen überstrapaziert, was zu seinem schlechten Ruf führt), aber manchmal ist es die einfachste und effizienteste Lösung.

Es gibt wahrscheinlich viele andere Möglichkeiten. Das Senden von Werten nur durch Kopieren ist noch ein weiteres und leicht zu überprüfen, aber ich denke, Sie sollten sich nicht nur auf diese Methode beschränken. Wir sind alle reif und können alle Dokumente lesen (vorausgesetzt, Sie dokumentieren Ihren Code richtig).

Das Go-Tool kommt auch mit einer sehr wertvollen race detector eingebaut, das in der Lage ist, Rennen zur Laufzeit zu erkennen. Schreiben Sie eine Menge Tests und führen Sie sie mit aktiviertem Renndetektor aus und nehmen Sie jede Fehlermeldung ernst. Sie weisen normalerweise auf ein schlechtes oder kompliziertes Design hin.

(PS: Vielleicht möchten Sie einen Blick auf Rust, wenn Sie einen Compiler und Typ System, die in der Lage, den gleichzeitigen Zugriff während der Kompilierzeit zu überprüfen, während freigegebenen Zustand noch erlaubt. Ich habe es nicht selbst verwendet, aber Die Ideen sehen ziemlich vielversprechend aus.)

+0

Dies sind alle gültigen Punkte, und sie würden funktionieren, wenn ich den gesamten Code selbst schreiben würde. Aber wenn meine Goroutine eine Funktion von einer externen Bibliothek aus ausführt, muss ich ihre Implementierung durchlaufen, um sicherzustellen, dass der Entwickler der Bibliothek auch den Vertrag einhält.Ich denke, dass es nicht viel einfacher ist, mich von externen Effekten zu isolieren, wenn ich keinen Staat teile. Gibt es ein Szenario, in dem meine vorgeschlagene Lösung noch unter Rennbedingungen laufen könnte? – tldr