Threadproblem - Keine Daten an Stream -> Thread wartet
-
So, wie Du das gezeigt und geschrieben hast, sollte es gehen (allerdings fehlt im Code nocht das putback des \n).
Allerdings sind zwei Dinge zu beachten:
1. Du solltest auch den Zugriff auf cin mit einem mutex sichern (oder eine eigene Klasse dafür bereit stellen).
2. Wann genau der Thread die Änderung des booleans mitbekommt ist m.W. nicht exakt definiert, da beide Threads auf separaten CPUs (oder cores einer CPU) laufen können. Wann die Variable aus dem Cache in den Hauptspeicher zurück geschrieben wird, hängt von der CPU ab. Bei x86 ist das m.W. in der Reihenfolge der Adressen, was bedeutet, dass der boolean vor dem \n in den Hauptspeicher zurück geschrieben werden kann, so dass der aus cin lesende Thread die Stop Bedingung erkennt, ohne dass noch ein \n bereit steht. Das wäre dann ein Deadlock in Folge eines Races.2. sollte sich aber mit 1. beheben lassen, da dann zumindest nach dem memory modell des neuen Standards - so wie ich das verstanden habe - das Zurückschreiben in exakt der Reihenfolge passiert, wie die Locks gesetzt werden.
-
Das habe ich jetzt nicht so ganz verstanden. Wenn ich den Zugriff auf cin mit einem Mutex absichere, dann kann ich doch gar kein putback machen, weil der Thread auf die Freigabe des Mutex wartet. Zumindest habe ich das so verstanden, dass du sowohl getline, als auch putback durch einen Mutex sichern willst.
Und zum bool: Hilft hier volatile oder hat das damit gar nichts zu tun? Ich hatte mal ein ähnliches Problem, da hat volatile geholfen.
-
314159265358979 schrieb:
ich habe eine kleine Frage. Wenn ich einen Thread habe, der laufend in einer Schleife mit getline aus einem Stream liest, dann ist diese Operation ja blockierend. Nun möchte ich aber diesen Thread von einem anderen Thread aus sauber beenden. Reicht es, wenn ich die Bedingung in der Schleife synchronisiert auf false ändere und in den Stream ein \n mit putback reinlege, oder gibt das putback hier Probleme?
Ja, das putback müsste ja von einem zweiten Thread aus geschehen und somit sollte es gar nicht möglich sein, da intern bereits die Instanz eines istream::sentry-Objekts existiert, welches in einigen Implementierungen den istream locked. Wenn es bei der von Dir verwendeten konkreten C++-IO-Lib doch möglich ist, wäre es ein Fehler, da der istream nicht dafür gemacht ist, zwei Methoden gleichzeitig zu verarbeiten - welche Klasse kann das schon!
Die Blockade geschieht normalerweise in den Methoden underflow oder uflow des std::streambuf. Und dort sollte man auch einhaken. Wenn Du etwa ein Socket-Device hast, so kannst Du es einfach von einem zweiten Thread aus schließen, was dann bei dem konkreten streambuf zu einem Fehler und damit zum Ende der Blockade und Rückgabe eines EOF führen sollte (ich unterstelle mal, es ist vernünftig programmiert). Dann kommt das getline zurück, Du solltest den istreamn auf fail testen, der ist dann fail, anschließend schaust Du nach, ob das Device noch offen ist, das ist nicht mehr offen, dann weißt Du dort, dass das kein Lesefehler sondern das Ende ist und Schluss.
Alternative: boost.asio
-
Du hast recht, ein Lock auf cin funktioniert in dem Fall nicht. Ich muss erst mal in den aktuellen Standard rein schauen, wie das memory modell in so einem Fall aussieht. Mit C++11 sollte es da aber einen Weg geben, dass die richtige Reihenfolge eingehalten wird.
Ein volatile hilft in dem Fall nicht, da es nur verhindert, dass die Variable in ein register Optimiert wird. Die write-back Reihenfolge vom cache zum Hauptspeicher beeinflusst das nicht (bei Java ist das anders).
-
@Werner Salomon: Das bedeutet, ich müsste mir also entweder einen eigenen streambuf schreiben, oder boost::asio verwenden? Einen eigenen streambuf traue ich mir nicht zu, daher tendiere ich zu boost::asio. Könntest du mir ein Stichwort geben, was du aus asio meinst? Meinst du die freien asynchronen Funktionen ala async_read_until()?
@gastgast: Vielleicht ließe sich da was mit atomics machen? Allerdings hat meine Implementierung (GCC 4.6) noch keine atomics soweit ich weiß. Hm...
Vielen Dank schon mal für eure Antworten

-
314159265358979 schrieb:
@Werner Salomon: Das bedeutet, ich müsste mir also entweder einen eigenen streambuf schreiben, oder boost::asio verwenden? Einen eigenen streambuf traue ich mir nicht zu, daher tendiere ich zu boost::asio. Könntest du mir ein Stichwort geben, was du aus asio meinst? Meinst du die freien asynchronen Funktionen ala async_read_until()?
Oh je - ich hatte zu spät gesehen, dass Du von std::cin liest. Da kenne ich keine 'fertige' Lösung. Es fällt mir spontan nur ein, sich selber mit direkten Zugriff über das Betriebssystem an der Tastatur zu bedienen ... auch nicht schön.
-
Werner Salomon schrieb:
Oh je - ich hatte zu spät gesehen, dass Du von std::cin liest. Da kenne ich keine 'fertige' Lösung. Es fällt mir spontan nur ein, sich selber mit direkten Zugriff über das Betriebssystem an der Tastatur zu bedienen ... auch nicht schön.
Das möchte ich möglichst vermeiden, da mein Projekt unter den meisten Unix Systemen (Mac OS X und Ubuntu auf jeden Fall, andere Linuxe wünschenswert) laufen sollte. Kompatibilität mit Windows werde ich aufgrund von anderen Dingen im Projekt aufgeben müssen.
-
314159265358979 schrieb:
Werner Salomon schrieb:
Oh je - ich hatte zu spät gesehen, dass Du von std::cin liest. Da kenne ich keine 'fertige' Lösung. Es fällt mir spontan nur ein, sich selber mit direkten Zugriff über das Betriebssystem an der Tastatur zu bedienen ... auch nicht schön.
Das möchte ich möglichst vermeiden, da mein Projekt unter den meisten Unix Systemen (Mac OS X und Ubuntu auf jeden Fall, andere Linuxe wünschenswert) laufen sollte. Kompatibilität mit Windows werde ich aufgrund von anderen Dingen im Projekt aufgeben müssen.
Wäre der direkte Weg unter Unixen nicht ein read() mit einer 0 als file descriptor? (0 = in, 1 = out, 2 = err)
Oder gehts noch direkter?Zumindestens wäre das auf wohl allen POSIX System brauchbar.
-
@gastgast: Vielleicht ließe sich da was mit atomics machen? Allerdings hat meine Implementierung (GCC 4.6) noch keine atomics soweit ich weiß. Hm...
Ja, <atomic> scheint das richtige zu sein, zumal da memory ordering explizit angegeben werden kann. In Verbindung mit dem Lock für den boolean wäre das der richtige Ansatz. Der VisualStudio C++ Compiler bringt die leider auch noch nicht mit.
-
Im Changelog von GCC 4.6 steht, dass es doch atomics gibt. Muss ich heute Abend, wenn ich Zuhause bin nochmal ausprobieren. Allerdings habe ich noch immer das eigentliche Problem mit getline. Ich möchte nur sehr ungern auf systemabhängige Funktionen zurückgreifen, ein gewisses Maß an Plattformunabhängigkeit hätte ich ganz gerne. Gibts denn keine Möglichkeit sowas zu bewerkstelligen?
-
@gast
Er schützt sein "stop" falg über ne Mutex, damit hat er automatisch acquire/release, d.h. es braucht keine Atomics mehr.----
Wie wenig kann man eigentlich von Multithreading verstehen, und immer noch der Meinung sein man hätte es verstanden?
------------------------
@314159265358979:
Wenn du erstmal dein Hirn einschalten würdest, bevor du Fragen wie diese hier postest, die du dir jederzeit leicht selbst hättest beantworten können, würden die Leute deine Threads vielleicht weniger "zuspammen".
Freund der Blasmusik, werd mal etwas selbständiger.
-
Könntest du bitte konkret auf den Punkt bringen, was ich deiner Meinung nach jetzt tun soll? Deine Aussage "werd mal etwas selbstständiger" bringt mich nicht wirklich weiter.
Weiters habe ich nirgens behauptet, Multithreading verstanden zu haben. Deshalb übe ich es, _damit_ ich das Zeug besser verstehe.
-
@314159265358979:
Oha, der "wie wenig ..." Satz war an "gast" gerichtet, nicht an dich!Ansonsten...
Wie, was du machen solltest?Du wolltest wissen ob es OK ist ... naja lies dein Kopfposting selbst nochmal.
Naja... sind Operationen auf Standard-Klassen ala iostream threadsafe?
Würdest du bei dem was du machen willst Funktionen aus zwei Threads gleichzeitig aufrufen?
Also, kann es dann OK sein was du machen willst?Bzw. falls du die Antworten nicht weisst, hättest du die Fragen dazu wenigstens mal selbst aufstellen können. Und dann googeln. Falls es dich genug interessiert deine eigene Zeit dafür zu opfern meine ich natürlich.
-
hustbaer schrieb:
Oha, der "wie wenig ..." Satz war an "gast" gerichtet, nicht an dich!
Das habe ich missverstanden, ich habe deine Striche ---- als Trennlinie zwischen dem was du zu gast und dem was du zu mir sagst interpretiert.
hustbaer schrieb:
Naja... sind Operationen auf Standard-Klassen ala iostream threadsafe?
Eigentlich nicht, nein.
hustbaer schrieb:
Würdest du bei dem was du machen willst Funktionen aus zwei Threads gleichzeitig aufrufen?
Ja, deswegen war ich mir ja unsicher.
hustbaer schrieb:
Also, kann es dann OK sein was du machen willst?
Da ein Thread hier wartet, war ich mir eben nicht sicher. Ich könnte mir gut vorstellen, dass eine Implementierung sowas erlaubt.
Ich wüsste ehrlich gesagt nicht, wonach ich googeln soll. Und ja, es interessiert mich genug, um es selbst zu tun

-
Also iostreams nicht threadsafe, du willst aber aus zwei Threads verwenden -> ganz klares "nicht OK".
Da ein Thread hier wartet, war ich mir eben nicht sicher. Ich könnte mir gut vorstellen, dass eine Implementierung sowas erlaubt.
Mal ganz abgesehen davon, dass es eh keinen Unterschied machen würde... wer sagt denn dass der wartet?
Vielleicht ist ja just in dem Moment echter User-Input dahergekommen, und der Stream ist gerade dabei seine Eingeweide zu sortieren für den nächsten Aufruf.Und ob eine Implementierung es erlaubt oder nicht, ist vollkommen egal.
Ich wüsste ehrlich gesagt nicht, wonach ich googeln soll.
Das war für den Fall gemeint, dass du nicht weisst welche iostream Funktionen in welcher weise thread-safe sind oder eben nicht.
-
Vielleicht hilft ja das weiter.
-
@hustbaer: Ja, eigentlich ist es logisch. Danke.
TyRoXx schrieb:
Vielleicht hilft ja das weiter.
An sowas habe ich auch schon gedacht, in meinem Fall mit select(). Allerdings wäre mir ein portabler weg lieber. Wenn nicht, dann werde ich es so machen, danke

-
@hustbaer: Ich wäre Dir dankbar, wenn Du statt zu polemisieren, mich auf meine Fehler hinweisen könntest - und die richtige Version lieferst. Ich lerne gerne dazu. Ironie spare ich mir jetzt...
-
Nachtrag @hustbaer:
Wie geschrieben:
2. Wann genau der Thread die Änderung des booleans mitbekommt ist m.W. nicht exakt definiert, da beide Threads auf separaten CPUs (oder cores einer CPU) laufen können. Wann die Variable aus dem Cache in den Hauptspeicher zurück geschrieben wird, hängt von der CPU ab. Bei x86 ist das m.W. in der Reihenfolge der Adressen, was bedeutet, dass der boolean vor dem \n in den Hauptspeicher zurück geschrieben werden kann, so dass der aus cin lesende Thread die Stop Bedingung erkennt, ohne dass noch ein \n bereit steht. Das wäre dann ein Deadlock in Folge eines Races.
Wo ist denn da der Fehler?
-
gastgast schrieb:
Nachtrag @hustbaer:
Wie geschrieben:
2. Wann genau der Thread die Änderung des booleans mitbekommt ist m.W. nicht exakt definiert, da beide Threads auf separaten CPUs (oder cores einer CPU) laufen können. Wann die Variable aus dem Cache in den Hauptspeicher zurück geschrieben wird, hängt von der CPU ab. Bei x86 ist das m.W. in der Reihenfolge der Adressen, was bedeutet, dass der boolean vor dem \n in den Hauptspeicher zurück geschrieben werden kann, so dass der aus cin lesende Thread die Stop Bedingung erkennt, ohne dass noch ein \n bereit steht. Das wäre dann ein Deadlock in Folge eines Races.
Wo ist denn da der Fehler?
Ja wie, wo ist der Fehler? Es stimmt halt einfach nicht. Ich hab doch schon geschrieben: er verwendet ne Mutex, und deswegen ist es kein Problem.
Das lock() auf ne Mutex macht ein "acquire", und das unlock() ein "release".
"acquire" und "release" sind sog. Memory-Fences, und garantieren als solche eine bestimmte "Sichtbarkeit" von Änderungen.
Grob vereinfacht kannst du dir denken, "acquire" heisst "Read Cache flushen" und "release" heisst "Write Cache flushen".
Oder, anderer Vergleich: "acquire" heisst "svn update" und "release" heisst "svn commit".
(Beide Vergleiche hinken vermutlich stark, aber für einfache Fälle sind sie vielleicht ne akzeptable Annäherung)Und was du über x86 und "Reihenfolge der Adressen" schreibst ist auch nicht richtig (was soll "Reihenfolge der Adressen" überhaupt heissen?).
x86 und AMD64 haben "total-store order". Das einzige was x86/AMD64 machen, ist hin und wieder mal ein "load" hinter ein "store" zu schieben. Was im Allgemeinen vollkommen unproblematisch ist.
Es gibt einige sog. "x86" CPUs die nicht nicht "total-store order" machen, aber alles was in den letzten 5 Jahren von Intel oder AMD produziert wurde sollte OK sein.
(Bei x86 gilt das nur für Datenzugriffe, bei AMD64 auch für das Lesen von Instructions)Davon abgesehen ist das relativ egal, so lange man kein Compiler-Reordering verhindert. Aber auch das übernehmen die in mutex_lock() und mutex_unlock() wohnenden Memory-Fences, d.h. auch das Compiler-Reordering ist kein Problem.
Und das alles völlig unabhängig von der Plattform.
(Einzige Voraussetzung ist, der verwendete Compiler muss grundsätzlich darauf ausgelegt sein "shared memory multithreading" zu ermöglichen.)
BTW: ein paar nette Papers die u.A. über solcherlei Dinge sprechen sind hier aufgelistet:
http://stackoverflow.com/questions/2001913/c0x-memory-model-and-speculative-loads-stores
(Die 5 Links in der Frage selbst)So. Und nu muss ich nochwas sagen. Ich finde es verwunderlich, wenn Leute eine Falschinformation schreiben, man sie dann darauf hinweist (einen zugegebenermassen knappen Hinweis darauf was falsch war bzw. warum), und sie sich dann (mehr oder weniger) erbosst und/oder erstaunt zeigen, dass man ihnen nicht gleich die nötigen Grundlagen mit erklärt, die sie bräuchten, um den Hinweis verstehen zu können.