Forum: Compiler & IDEs Hartnäckiger Heisenbug auch ohne Optimierung, am PC (x86_64)


von Erwin M. (nobodyy)


Lesenswert?

Ich habe ein C-Programm unter Linux laufen bei dem sich ein Heisenbug 
zeigt: Zuerst funktionierte damit die 4 Eingangs-Pins von einer 
seriellen Schnittstelle (Ri, ...) einzulesen, aber nach ein bischen 
Ausbauen zeigt sich ein Heisenbug, auch ohne Optimierung: Wird das 
Programm ohne

#define DEBUG

compiliert, werden keine Daten von dem Lese-Thread eingelesen, mit dem 
define werden sie korrekt eingelesen, obwohl das Define nur zusätzliche 
Ausgaben macht, wie der Name sagt zum Debuggen.
Deshalb habe ich mit Intervallhalbierung die Zeile gefunden, die den 
Unterschied zwiscnen funktionieren/nicht funktionieren ausmacht und es 
ist die Zeile

(void) fprintf (stderr, "main: Created thread number %d, for parallel 
reading from serial device %d.\n", gi_pt_number, i);

Das heißt ist die Zeile auskommentiert funktioniert das Einlesen nicht, 
ist sie nicht auskommentiert, funktioniert es.

Aber da wird nichts an der Programmausführung modifiziert, wie kann das 
sein dass es doch die Programmausführung massiv beeinflußt?

Zum Kompilieren verwende ich:

gcc -Wall -D_REENTRANT -lm -pthread -O0 -o logger logger.c -lrt

: Bearbeitet durch User
von Dussel (Gast)


Lesenswert?

Kannst du das Programm als Anhang hochladen? Erfahrungsgemäß würde ich 
nicht darauf vertrauen, dass das Problem in der genannten Zeile liegt. 
Oft wird der eigentliche Fehler nur verdeckt, wenn man eine Zeile 
auskommentiert, aber der Fehler liegt an einer ganz anderen Stelle.
Bist du sicher, dass du in den Debuganweisungen nur Ausgaben machst und 
nicht aus Versehen irgendwas wie if(a=b) oder eine Funktion mit 
Seiteneffekten ausführst?

von Nase (Gast)


Lesenswert?

Sind alle funktionen, die du verwendest, eintrittsinvariant oder 
Thread-sicher?

Warum überhaupt Threads?

von Peter II (Gast)


Lesenswert?

mit fprintf schaffst du eine Synchronisierung zwischen den Threads weil 
immer nur ein Thread auf stderr schreiben kann.

Das deutet darauf hin, das ein irgendwo nicht Thread Save bist. z.b. 
Globale Variablen

von Erwin M. (nobodyy)


Lesenswert?

Nase schrieb:
> Sind alle funktionen, die du verwendest, eintrittsinvariant oder
> Thread-sicher?

Ja, ich achte darauf indem die Threads eine eigene Aufgabe bekommen.


> Warum überhaupt Threads?

Ganz einfach: Für jede parallele Aufgab ein Thread.

von Erwin M. (nobodyy)


Lesenswert?

Peter II schrieb:
> mit fprintf schaffst du eine Synchronisierung zwischen den Threads weil
> immer nur ein Thread auf stderr schreiben kann.
>
> Das deutet darauf hin, das ein irgendwo nicht Thread Save bist. z.b.
> Globale Variablen

Die betreffende Zeile wird nur beim Start eines Threads aktiv, einmal. 
Aber sie verhindert dauerhaft das ein Tread Daten einliest.

von g457 (Gast)


Lesenswert?

> Ja, ich achte darauf indem die Threads eine eigene Aufgabe bekommen.

∗hüstel∗ du weisst schon was thread-sicher ist, oder?

von Nase (Gast)


Lesenswert?

Erwin Meyer schrieb:
>> Warum überhaupt Threads?
>
> Ganz einfach: Für jede parallele Aufgab ein Thread.
Das ist keine Rechtfertigung für Threads, sondern eine Ausrede für 
misslungene Architektur...

Erwin Meyer schrieb:
> Ja, ich achte darauf indem die Threads eine eigene Aufgabe bekommen.
Und das wiederum könnte eine gute Begründung dafür sein, dass du keine 
Threads nehmen solltest.

von Erwin M. (nobodyy)


Lesenswert?

Inzwischen habe ich den Fehler gefunden: Das letzte Argument bei 
pthread_create war immer nur eine Variable, wie bei Call by value 
üblich, aber die Funktion macht Call by reference. Durch das Hochzählen 
dieser Variablen und verwenden für alle Posix-Threads stimmten die IDs 
der Threads unter Umständen nicht mehr. Mal funktioniert es, mal nicht.
Mit einem Datenfeld mit index = Wert ist das Problem sauber gelöst.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Du solltest trotzdem Deine Methodik überdenken:

Wenn alle Threads etwas anderes machen und sie überhaupt nicht 
miteinander interagieren, dann wären X Prozesse statt X Threads doch 
angebrachter. Jeder Prozess hat dann seinen eigenen Adressraum und alle 
können friedlich nebeineinander koexistieren.

von Peter II (Gast)


Lesenswert?

Frank M. schrieb:
> Wenn alle Threads etwas anderes machen und sie überhaupt nicht
> miteinander interagieren, dann wären X Prozesse statt X Threads doch
> angebrachter. Jeder Prozess hat dann seinen eigenen Adressraum und alle
> können friedlich nebeineinander koexistieren.

nein, warum sollte man diesen krampf machen? Das hat man doch nur als 
Zwischenlösung gemacht, als es in Linux noch keine Threads gab.

Der Anwender möchte ein Programm starten und beenden, nicht die Prozesse 
durchsuchen ob es irgendwo doch noch Leichen gibt.

Da kostet das auch mehr Ressourcen, weil das Programm dann mehrfach ein 
Speicher ist. (ok das macht nicht so viel aus).

Und es wird bestimmt noch mehr Gemeinsamkeiten geben, und was es das 
logfile ist.

von F. F. (foldi)


Lesenswert?

Erwin Meyer schrieb:
> Inzwischen habe ich den Fehler gefunden: Das letzte Argument bei
> pthread_create war immer nur eine Variable, wie bei Call by value
> üblich, aber die Funktion macht Call by reference. Durch das Hochzählen
> dieser Variablen und verwenden für alle Posix-Threads stimmten die IDs
> der Threads unter Umständen nicht mehr. Mal funktioniert es, mal nicht.
> Mit einem Datenfeld mit index = Wert ist das Problem sauber gelöst.

In Amerika dürfen die Studenten an einer bestimmten Universität erst mit 
dem Prof. sprechen, wenn sie ihr Problem zuvor einem Teddy erzählt 
haben.
In deinem Fall war der Teddy das Forum. ;-)

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Peter II schrieb:
> nein, warum sollte man diesen krampf machen? Das hat man doch nur als
> Zwischenlösung gemacht, als es in Linux noch keine Threads gab.

Komisch, wenn ich ein aktuelles Linux starte, gibt es da immer (viel!) 
mehr als einen Prozess, der läuft? Mit Deiner Argumentation müsste man 
ja alles (z.B. apache, postfix, bind usw.) in einen Prozess (nennen wir 
ihn init ;-) ) stecken und alle Dienste als Thread laufen lassen. Wenn 
ich Linux herunterfahren will, reicht es dann, init zu killen - nach 
Deiner Argumentation. Geil! ;-)

Und warum betrachtest Du X Prozesse als Krampf? Zum Krampf wird es doch, 
wenn ich jeden Threads davor schützen muss, dass ein anderer Thread 
nicht gerade seine globalen Variablen zerhackstückt...

> Da kostet das auch mehr Ressourcen, weil das Programm dann mehrfach ein
> Speicher ist. (ok das macht nicht so viel aus).

Unsinn. Stichwort ist dabei Copy on Write. Neue Pages werden nach dem 
fork/exec erst dann angefordert, wenn sie auch mit (unterschiedlichen) 
Daten gefüllt werden.

> Und es wird bestimmt noch mehr Gemeinsamkeiten geben, und was es das
> logfile ist.

Sorry, das ist sehr wohl alles auch mit X Prozessen machbar und 
überhaupt kein Problem.

von Eric B. (beric)


Lesenswert?

Erwin Meyer schrieb:
> Inzwischen habe ich den Fehler gefunden: Das letzte Argument bei
> pthread_create war immer nur eine Variable, wie bei Call by value
> üblich, aber die Funktion macht Call by reference. Durch das Hochzählen
> dieser Variablen und verwenden für alle Posix-Threads stimmten die IDs
> der Threads unter Umständen nicht mehr.

Mja, Peter II (Gast) schrieb auch schon:

> Das deutet darauf hin, das ein irgendwo nicht Thread Save bist. z.b.
> Globale Variablen

(Auch wenn er Thread-Safe gemeint hat)

von Peter II (Gast)


Lesenswert?

Frank M. schrieb:
> Mit Deiner Argumentation müsste man
> ja alles (z.B. apache, postfix, bind usw.) in einen Prozess (nennen wir
> ihn init ;-) ) stecken und alle Dienste als Thread laufen lassen. Wenn
> ich Linux herunterfahren will, reicht es dann, init zu killen - nach
> Deiner Argumentation. Geil! ;-)

nein, ich will aber das der Webserver was für ein eigenständiges 
Programm ist als ein Prozess läuft.

> Und warum betrachtest Du X Prozesse als Krampf? Zum Krampf wird es doch,
> wenn ich jeden Threads davor schützen muss, dass ein anderer Thread
> nicht gerade seine globalen Variablen zerhackstückt...

wenn man gleich sinnvoll programmiert, hat man fast keine globalen 
Variablen.


> > Und es wird bestimmt noch mehr Gemeinsamkeiten geben, und was es das
> > logfile ist.
> Sorry, das ist sehr wohl alles auch mit X Prozessen machbar und
> überhaupt kein Problem.
klar ist das machbar, aber nur mit Krampf. On-the-Fly in ein neues 
Logfile schreiben geht schlecht, weil man denn erst jeden Prozess 
darüber informieren müsste das es jetzt ein neues Handle gibt.

Oder ist bei dir das Logging auch gleich ein neue Prozess?

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Peter II schrieb:
> nein, ich will aber das der Webserver was für ein eigenständiges
> Programm ist als ein Prozess läuft.

Aha. Also was hast Du an meinem Satz oben

   "Wenn alle Threads etwas anderes machen ..."

(auf den Du reagiertest)

nicht verstanden?

> klar ist das machbar, aber nur mit Krampf. On-the-Fly in ein neues
> Logfile schreiben geht schlecht, weil man denn erst jeden Prozess
> darüber informieren müsste das es jetzt ein neues Handle gibt.

man syslog.

von Peter II (Gast)


Lesenswert?

Frank M. schrieb:
> Aha. Also was hast Du an meinem Satz oben
>
>    "Wenn alle Threads etwas anderes machen ..."
>
> (auf den Du reagiertest)

ein Thread lesen der Daten, ein Thread verarbeiten der Daten und ein 
Thread für die Ausgabe.

alles 3 Threads machen was anderes, trotzdem würde ich nicht auf die 
Idee kommen sie als 3 Prozesse auszuführen.

> man syslog.

toll - das vermutlich nicht das was man unter logging eines Prozesses 
versteht.

Apache, Mysql, Bind, squid nutzen es schon mal nicht für log - wird wohl 
einen Grund haben.

von Nase (Gast)


Lesenswert?

Peter II schrieb:
> Frank M. schrieb:
>> Aha. Also was hast Du an meinem Satz oben
>>
>>    "Wenn alle Threads etwas anderes machen ..."
>>
>> (auf den Du reagiertest)
>
> ein Thread lesen der Daten, ein Thread verarbeiten der Daten und ein
> Thread für die Ausgabe.
>
> alles 3 Threads machen was anderes, trotzdem würde ich nicht auf die
> Idee kommen sie als 3 Prozesse auszuführen.

Und vermutlich schleppst du mit drei Threads mehr Unwägbarkeiten in dein 
Programm, als es ein schlichter Zustandsautomat tun würde.

Skaliert dein Problem gut mit Threads?
Wenn nur ein Thread Daten lesen kann und auch nur ein Thread diese 
Daten verarbeitet, dann skaliert es überhaupt nicht. Dann braucht man 
auch keine Threads.

von Erwin M. (nobodyy)


Lesenswert?

Nase schrieb:

> Skaliert dein Problem gut mit Threads?

Ja, mit 0 bis 21 Threads läuft es problemlos, obwohl meine Rechner nur 4 
bis 8 Cores haben.


> Wenn nur ein Thread Daten lesen kann und auch nur ein Thread diese
> Daten verarbeitet, dann skaliert es überhaupt nicht.

Doch gerade dann sollte man Threads einsetzen, genau einen pro 
Schnittstelle, mit einem Ringpuffer in den nur dieser Tread T hinein 
schreibt und aus dem X andere nur lesen (meist nur main, aber andere 
können auch Auslesen), wobei T den offizellen Index auf die neuesten 
Daten erst inkrementiert nachdem sie in den Ringpuffer geschrieben 
wurden, um Locks einzusparen.

Beispielsweise Polling: Ein Thread k schicht als Timer-Thread x anderen 
ein Signal wenn es wieder Zeit ist zum Einlesen (sonst macht dieser 
Thread nichts/schlafen) und das Einlesen machen dann diese x 
aufgeweckten Threads parallel von x Schnittstellen, z. B. Parallelports.
Für wenig Jitter sollte man wenig load und x < Core-Anzahl haben (und 
lowlatency- oder realtime-Kernel, hohe Priorität etc.). Mit in der 
Praxis um 100 µs Jitter mit einem Lowlatency-Kernel auf einem billigen 
PC reicht das schon für viele Zwecke.

von Erwin M. (nobodyy)


Lesenswert?

Eric B. schrieb:
> Mja, Peter II (Gast) schrieb auch schon:
>
>> Das deutet darauf hin, das ein irgendwo nicht Thread Save bist. z.b.
>> Globale Variablen
>
> (Auch wenn er Thread-Safe gemeint hat)

Damit lag er falsch, denn es war ein Laufzeiteffekt: Das printf bewirkte 
das main länger brauchte und in der Zeit hat der Thread den übergebenen 
Paramter einlesen und verarbeiten könen. Ohne das printf war main 
schneller und modifizierte den Parameter bevor der neue Thread den 
Parameter verarbeitet hat.
Das hat also mit globalen Variablen nichts zu tun, sondern war schlicht 
Call bei value angewendet wo tatsächlich Call by reference gemacht wird.
Da könnte der GCC eigentlich eine Warnung ausgeben weil hier eine Race 
Condition vorliegt, aber bisher macht er das nicht.

von Erwin M. (nobodyy)


Lesenswert?

Frank M. schrieb:
> Du solltest trotzdem Deine Methodik überdenken:
>
> Wenn alle Threads etwas anderes machen und sie überhaupt nicht
> miteinander interagieren,

Das ist ja nicht der Fall, sonst wäre es ja konsequenterweise nicht ein 
Programm. Beispielsweise tauschen die Threads Signale aus und schreiben 
eingelesene Daten in Ringpuffer, die (bisher nur) main 
auswertet/ausgibt. Es gibt da also mehrere Kopplungen.

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Erwin Meyer schrieb:
> Das heißt ist die Zeile auskommentiert funktioniert das Einlesen nicht,
> ist sie nicht auskommentiert, funktioniert es.

Das ist aber kein Heisen-Bug ...

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.