Hallo Leute,
Ich hab folgendes Probleme:
Es gibt 2 Threads, 1 Thread schreibt etwas in eine Queue rein, und 1
anderer Thread liest aus der Queue. Leider stuertzt das ganze hin und
wieder mit einem SIGSEGV (Segmentation fault) ab. Mir leuchtet
allerdings nicht ein weshalb.
OS: Arch Linux Kernel 3.18.14/4.0.4
Compiler: GCC 4.9.2/5.1.0
Einschraenkungen:
- Kein Boost
- muss mit -std=c++98 compilieren
std::stringtmp=FooQueue.front();// <- SegFault wenn Queue leer
76
FooQueue.pop();
77
...
78
// do any
79
}
80
}
81
82
//----------------------------------------
83
84
intmain(void)
85
{
86
FooBarfb;
87
88
fb.Run();
89
90
return0;
91
}
Zu einem SegFault kommt es, wenn die Queue leer ist und ich versuche
daraus zu lesen. Dieser Fall ist doch aber durch die vorgelagerte
while-Schleife (meiner Meinung nach) ausgeschlossen, oder nicht? Es
Funktioniert ja auch in 99% der Zeit, aber eben nur in 99%...
Kann jemand erklaeren wie es zu dem SegFault kommen kann, und wie man
das vernuenftig absichert? :-/
Gruesse
Kaj G. schrieb:> Kann jemand erklaeren wie es zu dem SegFault kommen kann, und wie man> das vernuenftig absichert? :-/
Threadsicher programmieren!
dein Lock ist unzureichend.
1
while((FooQueue.empty()==true)||(Lock==true))
2
{
3
}
danach kann Lock schon wieder auf true sein und dann knallt es.
Du brauchst eine ResourceGuard, kann man z.b. mit eine Mutex oder unter
Windows mit einer CriticalSection erreichen.
Jeder zugriff auf die Liste darf nur von einen Thread zu jedes Zeitpunkt
gemacht werden.
Ich habe mir deinen Code jetzt nicht genau angeschaut aber es fällt mir
schonmal auf, dass es mit deinem bool Flag nicht ganz richtig ist. Die
Reihenfolge in der Threads das geänderte Flag sehen kann ziemlich wirr
sein, daher nimmt man dafür ein pthread_mutex_t und
pthread_mutex_lock/pthread_mutex_unlock.
Jürgen schrieb:> Evtl. funktioniert auch schon ein:> volatile bool Lock;
Nein.
Bei wirklich jedem nebenläufigen Problem kommt irgendein MC-Muckler aus
einer Ecke hervor und singt das Hohelied des volatile.
Könnt ihr bitte endlich mal in eure Rübe kriegen, daß volatile total
unterspezifiziert ist, in eurem
Globale-Variablen-und-Interrupt-Standard-Beispiel mehr zufällig
funktioniert, und bei solchen Problemen niemals funktionieren kann?
Wie immer gilt: Wenn deine Antwort "volatile" lautet, dann hast du die
Frage nicht verstanden!
Hehe, wollte gerade editieren (geht aber anscheinend als Gast nicht).
Ja ihr habt alle recht in diesem Fall gehts nur über ein Mutex oder
ähnliche Mechanismen.
... und warten am besten mit pthread_cond_wait() bzw.
pthread_cond_timedwait() - wenn schon, dann richtig und die
Warteschleife auch gleich weg-optimieren.
Jürgen schrieb:> und warten am besten mit pthread_cond_wait() bzw.> pthread_cond_timedwait() - wenn schon, dann richtig und die> Warteschleife auch gleich weg-optimieren.
braucht er gar nicht bei einem Mutex, der blockiert ja schon selber.
Marc schrieb:> Nein.>> Bei wirklich jedem nebenläufigen Problem kommt irgendein MC-Muckler aus> einer Ecke hervor und singt das Hohelied des volatile.>> Könnt ihr bitte endlich mal in eure Rübe kriegen, daß volatile total> unterspezifiziert ist, in eurem> Globale-Variablen-und-Interrupt-Standard-Beispiel mehr zufällig> funktioniert, und bei solchen Problemen niemals funktionieren kann?>> Wie immer gilt: Wenn deine Antwort "volatile" lautet, dann hast du die> Frage nicht verstanden!
Das ist wiederum zu pauschal.
Es gibt durchaus Fälle wo man volatile braucht, wenn auch nicht als
Ersatz für einen Mutex (sondern ggf. zusätzlich).
Das "Wie immer gilt" ist genauso falsch wie das Gegenteil.
Peter II schrieb:> Jürgen schrieb:>> und warten am besten mit pthread_cond_wait() bzw.>> pthread_cond_timedwait() - wenn schon, dann richtig und die>> Warteschleife auch gleich weg-optimieren.>> braucht er gar nicht bei einem Mutex, der blockiert ja schon selber.
Ich hab irgendwie immer noch nicht verstanden, wozu man
Condition-Variablen überhaupt braucht. Was machen die denn, das man
nicht mit Mutexen oder Semaphoren genauso gut machen könnte?
Rolf Magnus schrieb:> Ich hab irgendwie immer noch nicht verstanden, wozu man> Condition-Variablen überhaupt braucht. Was machen die denn, das man> nicht mit Mutexen oder Semaphoren genauso gut machen könnte?
kann ich jetzt auch nicht genau sagen. Ich finde das ganze
posix-thread-zeugt nicht schön.
Das finde ich Windows mit dienen Events, CriticalSections irgendwie
einfacher. Um eine Windows Funktionalität nachzubauen habe ich mal so
eine Condition-Variable gebraucht.
Das "übliche" Verfahren ist: mit einem mutex, die Zugriffe auf die queue
zu synchronisieren. Damit der thread, der aus der queue Elemente nimmt,
für den Fall, dass die Queue leer ist, die queue nicht pollen muss,
verwendest Du eine condition variable (cv), auf der der thread warten
kann (in einer Schleife, dass predicate der cv prüfend). Diese cv
signalisierst Du dann, wenn Du Elemente in die queue steckst. Also immer
dann, wenn das predicate der cv wahr ist (queue nicht leer).
Buchempfehlung: "Programming with POSIX Threads". Da boost threads und
std::thread nach den gleichen Prinzipien wie POSIX Threads funktioniert,
kann man das Buch auch gut lesen, wenn man nicht vor hat, mit posix zu
entwickeln.
Torsten Robitzki schrieb:> ... verwendest Du eine condition variable (cv), auf der der thread warten> kann (in einer Schleife, dass predicate der cv prüfend).
... die i.d.R. wiederum mit einem eigenen Mutex gesichert werden muß.
Klaus Wachtler schrieb:> Torsten Robitzki schrieb:>> ... verwendest Du eine condition variable (cv), auf der der thread warten>> kann (in einer Schleife, dass predicate der cv prüfend).>> ... die i.d.R. wiederum mit einem eigenen Mutex gesichert werden muß.
Nein, Du kannst den gleichen benutzen. Der Mutex der cv ist dazu da, das
predicate zu sichern. Da das predicate aber einfach queue.empty() ist,
sollte es sogar der selbe mutex sein.
Darüber kann man streiten.
Wenn es nicht ganz einfach ist, dann kann es sein, daß der Zugriff auf
Queue und condition-Variable nicht deckungsleich sind und evtl. noch
zwischen Lese- und Schreibzugriffen unterschieden werden muß.
Spätestens dann macht ein Mutex für beides keinen Sinn mehr.
Bei nur ja/nein macht die Queue wiederum wenig Sinn.
Hallo Klaus,
Klaus Wachtler schrieb:> Darüber kann man streiten.
Wenn man den konkreten Fall betrachtet, dann kann man darüber zwar
streiten aber es kommt immer das gleiche dabei heraus ;-)
ganz einfaches Beispiel einer synchronisierten, nicht limitierten Queue:
1
#include<condition_variable>
2
#include<mutex>
3
#include<deque>
4
5
template<classT>
6
classqueue
7
{
8
public:
9
voidpush(constT&value)
10
{
11
std::lock_guard<std::mutex>lock(mutex_);
12
queue_.push_back(value);
13
cv_.notify_one();
14
}
15
16
Tpop()
17
{
18
std::unique_lock<std::mutex>lock(mutex_);
19
20
while(!queue_.empty())
21
cv_.wait(lock);
22
23
constTresult=queue_.front();
24
queue_.pop_front();
25
26
returnresult;
27
}
28
29
private:
30
std::deque<T>queue_;
31
std::mutexmutex_;
32
std::condition_variablecv_;
33
};
wo würdest Du da noch Raum für einen zweiten Mutex sehen? Oder verstehen
wir die Aufgabenstellung unterschiedlich?
mfg Torsten
Rolf Magnus schrieb:> Ich hab irgendwie immer noch nicht verstanden, wozu man> Condition-Variablen überhaupt braucht. Was machen die denn, das man> nicht mit Mutexen oder Semaphoren genauso gut machen könnte?
Condition variablen sind etwas generischer und effektiver als
Semaphoren. Bei einer Semaphore, ist dass auf das man warten kann immer
der Zähler, bei einer condition variable ist die Wartebedingung immer
explizit ausserhalb der cv definiert, die cv dient nur dazu, darauf zu
warten.
Aus einer condition variable, einem mutex und einem integer kann man
sich eine semaphore bauen.
Man kann eine Semaphore aber auch als mutex verwenden. Man kann mit
einer Semaphore auch eine condition variable bauen.
Nur mit einem mutex kann man aber in der Regel (weil lock/unlock vom
gleichen thread zu passieren hat) keine cv oder semaphore bauen.
Torsten Robitzki schrieb:> Oder verstehen> wir die Aufgabenstellung unterschiedlich?
Mag sein.
Es wäre auch noch zu klären, ob wir jetzt genau über dieses Beispiel vom
TO reden, oder allgemeiner ("Das übliche Verfahren...").
Falls ich nicht gerade ganz verwirrt bin:
In deinem Beispiel setzt du in pop() gleich pauschal den einzigen Mutex
und wartest.
Wie soll push() dann noch etwas in die Queue schreiben können und
signalisieren, wenn es doch auf die Freigabe des Mutex warten muß?
Klaus Wachtler schrieb:> Wie soll push() dann noch etwas in die Queue schreiben können und> signalisieren, wenn es doch auf die Freigabe des Mutex warten muß?
wait() gibt während des Wartens den Mutex wieder frei und locked ihn
wieder, wenn die Funktion zurück kehrt. Deswegen ist die cv auch immer
mit einem mutex assoziiert (Entwerter als Argument zu wait() bei std und
boost oder im Konstruktor bei Posix).
Hinter der while Schleife ist der mutex also wieder gelocked und die
queue nicht leer (das predicate erfüllt).
Ich danke euch soweit erstmal für eure hilfe. :)
Leider gibt es auch hier das übliche Problem: lesen! :P
Denn ganz zu beginn schrieb ich bereits:
Kaj G. schrieb:> Einschraenkungen:> - Kein Boost> - muss mit -std=c++98 compilieren
Und soweit ich das von http://www.cplusplus.com und
http://de.cppreference.com/w/ entnehmen kann sind sachen wie std::mutex
und Co. C++11, fallen damit also raus. Trotzdem danke für die hinweise.
:)
Hab jetzt ein bisschen mit pthread_mutex_lock() und
pthread_mutex_unlock() probiert.
Die gute Nachricht: Das Programm stürtzt nicht mehr ab. :)
Die schlechte: Es kommt früher oder später zu einem Deadlock, was nicht
wirklich verwunderlich ist. Jaja, wenn man halt keine Ahnung hat... :-/
Der Code sieht in etwa so aus:
Kaj G. schrieb:> Und soweit ich das von http://www.cplusplus.com und> http://de.cppreference.com/w/ entnehmen kann sind sachen wie std::mutex> und Co. C++11, fallen damit also raus. Trotzdem danke für die hinweise.
C++11 implementiert genau das gleiche, wie Posix. Die Dinger haben da
sogar exakt den gleichen Namen. Nimm einfach eine condition variable,
dann hast Du auch eine Garantie das es funktionieren wird. Du kannst
einfach mein Beispiel nehmen und es nach Posix übersetzen (std::mutex ->
pthread_mutex_*, std::condition_variable ->pthread_cond_*).
Entschuldige, wenn ich zu faul war, dass für Dich zu machen ;-)
Ein try lock verhindert auch nicht, dass Du in `while(FooQueue.empty())`
ohne Synchronisation auf die Queue zugreifst.
Marc schrieb:> Bei wirklich jedem nebenläufigen Problem kommt irgendein MC-Muckler aus> einer Ecke hervor und singt das Hohelied des volatile.
Ja, und? Das ist das Mittel der Wahl um Optimierungen zu unterdrücken
bzw. gegen gewisse Hazards abzusichern. Gerade im Zusammenhang mit der
volatile-correctness bei Klassen kann volatile doch sehr nützlich sein.
> Könnt ihr bitte endlich mal in eure Rübe kriegen, daß volatile total> unterspezifiziert ist, in eurem> Globale-Variablen-und-Interrupt-Standard-Beispiel mehr zufällig> funktioniert, und bei solchen Problemen niemals funktionieren kann?
Also alles falsch, was die sich so für den C++ Standard so ausdenken und
Du hast das Patentrezept?
Ich finde volatile sehr nützlich. Schönes Beispiel wofür z.B. auf:
http://www.drdobbs.com/cpp/volatile-the-multithreaded-programmers-b/184403766
db8fs schrieb:> http://www.drdobbs.com/cpp/volatile-the-multithreaded-programmers-b/184403766
Der Beitrag hatte damals (guck mal auf's Datum) für Furore in der
englisch sprachigen Newsgroup gesorgt. Tenor war: das wäre nett, wenn es
funktionieren würde (oder mal sogar eigene qualifier definieren könnte),
aber das volatile unterbindet im besten Fall Optimierungen.
Moin
Torsten Robitzki schrieb:> ganz einfaches Beispiel einer synchronisierten, nicht limitierten Queue:
Nach ein bisschen diskussion konnte ich erreichen das ich C++11 nutzen
darf, somit hab ich dein Beispiel mal ausprobiert. Es fuehrt zu einem
SegFault wenn man die Queue mit std::string benutzt. Warum?
ganz einfach:
1
Tpop()
2
{
3
std::unique_lock<std::mutex>lock(mutex_);
4
5
while(!queue_.empty())
6
cv_.wait(lock);
7
8
constTresult=queue_.front();
9
queue_.pop_front();
10
11
returnresult;
12
}
Wenn die Queue leer ist (was gerade am Anfang meistens der Fall ist)
wird direkt versucht das erste Element, welches gar nicht existiert, zu
popen. Boom! SegFault!
Die negation muesste weg, wo mit dann aber auch die schleife unnoetig
wird, weil wait() ja eh wartet.
(Das die Schleife weg kann, wurde hier schon irgendwo geschrieben.)
1
Tpop()
2
{
3
std::unique_lock<std::mutex>lock(mutex_);
4
5
cv_.wait(lock);
6
7
constTresult=queue_.front();
8
queue_.pop_front();
9
10
returnresult;
11
}
Bitte berichtigt mich, wenn ich flasch liege.
Gruesse
Moin Moin,
Kaj G. schrieb:> Die negation muesste weg, wo mit dann aber auch die schleife unnoetig> wird, weil wait() ja eh wartet.
Richtig, entschuldige, dass ich Dir keinen getestet und fehlerfreien
Code geliefert habe. Die schleife darf auf keinen Fall weg. Das sollte
aber auch in jeder Dokumentation zu condition variables stehen, sei es
nun C++ oder POSIX. Es gibt Situationen, in denen kehrt das wait()
zurück, ohne das das predicate wahr ist (spurious wakeup). Wenn die
Schleife verlassen wurde, dann weil das predicate wahr ist. Und die
Prüfung auf das predicate wurde mit gelocktem mutex gemacht. Sprich:
wenn die Schleife verlassen ist, ist die Bedingung, auf die Du wartest
wahr und der Mutex ist locked.
> (Das die Schleife weg kann, wurde hier schon irgendwo geschrieben.)
Kann sein, dass das jemand im Internet geschrieben hat. Aber
entscheidend ist, was die Dokumentation sagt:
http://en.cppreference.com/w/cpp/thread/condition_variable/wait
Jaja, ich weis, ist auch das Internet ;-)
mfg Torsten