Forum: Compiler & IDEs C++ Problem mit Threadsicherheit und std::queue<std::string>


von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

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
1
#include <queue>
2
#include <string>
3
4
#include <pthread.h>
5
6
class FooBar
7
{
8
  public:
9
    FooBar();
10
    ~FooBar();
11
12
    void Run(void);
13
    void* Bar(void);
14
    void* Foo(void);
15
16
    static void* threaded_Bar(void* classRef)
17
    {
18
      return ((FooBar*)classRef)->Bar();
19
    }
20
21
    static void* threaded_Foo(void* classRef)
22
    {
23
      return ((FooBar*)classRef)->Foo();
24
    }
25
26
  private:
27
    bool Lock;
28
    std::queue<std::string> FooQueue;
29
}
30
31
//----------------------------------------
32
33
FooBar::FooBar()
34
{
35
  Lock = false;
36
}
37
38
FooBar::~FooBar()
39
{
40
41
}
42
43
void FooBar::Run(void)
44
{
45
  pthread_t t1, t2;
46
  pthread_create(&t1, NULL, FooBar::threaded_Bar, this);
47
  pthread_create(&t2, NULL, FooBar::threaded_Foo, this);
48
49
  pthread_join(t1, NULL):
50
  pthread_join(t2, NULL);
51
}
52
53
void* FooBar::Bar(void)
54
{
55
  while(true)
56
  {
57
    ...
58
    // do any
59
    ...
60
    Lock = true;
61
    FooQueue.push("FooBar");
62
    Lock = false;
63
    ...
64
    // do more
65
  }
66
}
67
68
void* FooBar::Foo(void)
69
{
70
  while(true)
71
  {
72
    while( (FooQueue.empty() == true) || (Lock == true) )
73
    {
74
    }
75
    std::string tmp = FooQueue.front(); // <- SegFault wenn Queue leer
76
    FooQueue.pop();
77
    ...
78
    // do any
79
  }
80
}
81
82
//----------------------------------------
83
84
int main(void)
85
{
86
  FooBar fb;
87
88
  fb.Run();
89
90
  return 0;
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

von Peter II (Gast)


Lesenswert?

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.

von Sebastian V. (sebi_s)


Lesenswert?

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.

von Rene H. (Gast)


Lesenswert?

Schau mal hier den Beispielcode an für mutex.

http://codereview.stackexchange.com/questions/49820/thread-safe-queue

Grüsse,
René

von Jürgen (Gast)


Lesenswert?

Evtl. funktioniert auch schon ein:
volatile bool Lock;

von Peter II (Gast)


Lesenswert?

Jürgen schrieb:
> Evtl. funktioniert auch schon ein:
> volatile bool Lock;

nein, bringt überhaupt nichts.

von Marc (Gast)


Lesenswert?

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!

von Jürgen (Gast)


Lesenswert?

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.

von Jürgen (Gast)


Lesenswert?

... und warten am besten mit pthread_cond_wait() bzw. 
pthread_cond_timedwait() - wenn schon, dann richtig und die 
Warteschleife auch gleich weg-optimieren.

von Peter II (Gast)


Lesenswert?

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.

von Klaus W. (mfgkw)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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?

von Peter II (Gast)


Lesenswert?

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.

von Klaus W. (mfgkw)


Lesenswert?

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?

http://stackoverflow.com/questions/12551341/when-is-a-conditional-variable-needed-isnt-a-mutex-enough

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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.

von Klaus W. (mfgkw)


Lesenswert?

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ß.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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.

von Klaus W. (mfgkw)


Lesenswert?

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.

von Andreas S. (andreas) (Admin) Benutzerseite


Lesenswert?

Eine Alternative wäre boost::lockfree:queue:
http://www.boost.org/doc/libs/1_58_0/doc/html/boost/lockfree/queue.html

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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 < class T >
6
class queue
7
{
8
public:
9
    void push( const T& value )
10
    {
11
        std::lock_guard< std::mutex > lock( mutex_ );
12
        queue_.push_back( value );
13
        cv_.notify_one();
14
    }
15
16
    T pop()
17
    {
18
        std::unique_lock< std::mutex > lock( mutex_ );
19
20
        while ( !queue_.empty() )
21
            cv_.wait( lock );
22
23
        const T result = queue_.front();
24
        queue_.pop_front();
25
26
        return result;
27
    }
28
29
private:
30
    std::deque< T >         queue_;
31
    std::mutex              mutex_;
32
    std::condition_variable cv_;
33
};

wo würdest Du da noch Raum für einen zweiten Mutex sehen? Oder verstehen 
wir die Aufgabenstellung unterschiedlich?

mfg Torsten

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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.

von Klaus W. (mfgkw)


Lesenswert?

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ß?

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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).

: Bearbeitet durch User
von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

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:
1
#include <queue>
2
#include <string>
3
4
#include <pthread.h>
5
6
class FooBar
7
{
8
  public:
9
    FooBar();
10
    ~FooBar();
11
12
    void Run(void);
13
    void* Bar(void);
14
    void* Foo(void);
15
16
    static void* threaded_Bar(void* classRef)
17
    {
18
      return ((FooBar*)classRef)->Bar();
19
    }
20
21
    static void* threaded_Foo(void* classRef)
22
    {
23
      return ((FooBar*)classRef)->Foo();
24
    }
25
26
  private:
27
    pthread_mutex_t Lock;
28
    std::queue<std::string> FooQueue;
29
}
30
31
//----------------------------------------
32
33
FooBar::FooBar()
34
{
35
  pthread_mutex_init(&Lock, NULL);
36
}
37
38
FooBar::~FooBar()
39
{
40
41
}
42
43
void FooBar::Run(void)
44
{
45
  pthread_t t1, t2;
46
  pthread_create(&t1, NULL, FooBar::threaded_Bar, this);
47
  pthread_create(&t2, NULL, FooBar::threaded_Foo, this);
48
49
  pthread_join(t1, NULL):
50
  pthread_join(t2, NULL);
51
52
  pthread_mutex_destroy(&Lock);
53
}
54
55
void* FooBar::Bar(void)
56
{
57
  while(true)
58
  {
59
    ...
60
    // do any
61
    ...
62
    pthread_mutex_lock(&Lock);
63
    FooQueue.push("FooBar");
64
    pthread_mutex_unlock(&Lock);
65
    ...
66
    // do more
67
  }
68
}
69
70
void* FooBar::Foo(void)
71
{
72
  while(true)
73
  {
74
    while(FooQueue.empty())
75
    {
76
    }
77
    pthread_mutex_lock(&Lock);
78
    std::string tmp = FooQueue.front(); // <- SegFault wenn Queue leer
79
    FooQueue.pop();
80
    pthread_mutex_unlock(&Lock);
81
    ...
82
    // do any
83
  }
84
}
Der Deadlock wird wohl dadurch kommen, das ich keine cv hab.
Sollte ich statt pthread_mutex_lock() lieber pthread_mutex_trylock() 
nutzen?
------------------------------------------
Edit:
Hab grad noch diesen Thread gefunden: 
http://stackoverflow.com/questions/23400097/c-confused-on-how-to-initialize-and-implement-a-pthread-mutex-and-condition-vari#23400417
Das werd ich am Montag mal probieren.

Vielen Dank für eure hilfe! :)

Grüße

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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.

von db8fs (Gast)


Lesenswert?

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

von Εrnst B. (ernst)


Lesenswert?

db8fs schrieb:
> Ich finde volatile sehr nützlich. Schönes Beispiel wofür z.B. auf:

Das klappt alles nur mit den Microsoft-Erweiterungen zu "volatile", die 
jedem volatile-Zugriff auch noch eine Memory-Barrier spendieren.

Ist aber nach dem C(++)-Std nicht erforderlich.

Dort wird nur gefordert, dass die Zugriffe auf volatile-Speicher vor dem 
nächsten Sequence Point ausgeführt werden. Zu Threads, Multi-Core, Cache 
usw. gibt es keine Aussagen.

https://software.intel.com/en-us/blogs/2007/11/30/volatile-almost-useless-for-multi-threaded-programming/

http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf (Kapitel 5)

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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.

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

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
    T pop()
2
    {
3
        std::unique_lock< std::mutex > lock( mutex_ );
4
5
        while ( !queue_.empty() )
6
            cv_.wait( lock );
7
8
        const T result = queue_.front();
9
        queue_.pop_front();
10
11
        return result;
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
    T pop()
2
    {
3
        std::unique_lock< std::mutex > lock( mutex_ );
4
5
        cv_.wait( lock );
6
7
        const T result = queue_.front();
8
        queue_.pop_front();
9
10
        return result;
11
    }
Bitte berichtigt mich, wenn ich flasch liege.

Gruesse

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.