PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : pthread: was genau macht "joinable" und was macht "detached"?



HaWe
12.06.2019, 19:00
hi
ich verstehe noch nicht den genauen Unterschied bei pthread:
was genau macht "joinable" bzw. pthread_join mit einem thread, wenn er sich beendet hat (oder wurde),
und was macht "detached"?

aktueller Anlass:
üblicherweise laufen fast alle meine threads in einer Dauerschleife (while (active) { // loop} ),
und wenn active extern auf 0 gesetzt wird, werden alle threads beendet und per pthread_join "gejoined" (was immer dann damit passiert).

was aber, wenn sich ein solcher thread aufhängt und daher von main() beendet, alle Einstellungen resettet und dann neu gestartet werden muss?
Geht das auch mit meinen "normalen joinable" threads, oder muss ich sie von vorn herein als detached ausführen?

schorsch_76
13.06.2019, 09:25
Hallo HaWe,

ein thread ist ein paralleler Ablauf "Faden".

joinable() gibt an, ob der Thread bereit ist für einen join(). Das ist er bsp. wenn er läuft oder er seine thread Funktion beendet hat (mit return oder einfach die Funktion verlassen hat).

Ein Thread ist nicht joinable(), wenn er detached gestartet wurde. Detached bedeutet, dass er sozusagen im Hintergrund in deinem Programm läuft. Dein Handle ist dann nicht mehr gültig und du hast keine "Verbindung" mehr zu ihm, außer dass du eventuell Syncronisationsobjekte oder atomare Variable hast, mit dem du mit ihm kommunizierst. Du machst einen Thread detached, wenn du dich nicht um seine Lebenszeit kümmern willst. ABER: Bei Programende must du doch irgendwie dafür sorgen, das er sauber beendet wird.

Meine Empfehlung: Starte ihn joinable und kümmer dich um seine Lebenszeit.

Bei boost [1] oder bei cppreference [2] kannst du noch mehr darüber erfahren.

[1] https://www.boost.org/doc/libs/1_70_0/doc/html/thread.html
[2] https://en.cppreference.com/w/cpp/thread/thread

HaWe
13.06.2019, 11:02
dankeschön, also wäre joinable die bessere Strategie...
join aber haißt doch "beitreten, verbinden" -
am Schluss will ich ihn aber doch beenden (gerade wenn er sich aufgehängt hat, um ihn dann neu zu starten):
wieso dann "join=beitreten" - mir ist das Konzept hinter join noch nicht klar.... :-k

Ceos
13.06.2019, 11:06
join aber haißt doch "beitreten, verbinden" -
am Schluss will ich ihn aber doch beenden (gerade wenn er sich aufgehängt hat), wieso dann "join" - mir ist das Konzept hinter join noch nicht klar.... :-k

Um deinen Thread "normal" zu beenden benötigst du eine "Möglichkeit" die Schleife zu beenden. Dafür gibt es sehr viele Lösungen, aber der Join bezieht sich in erster Linie darauf dass du deinen aktuellen Thread Kontext (von dem aus du den anderen beenden möchtest) mit dem Kontext deines laufenden Thread synchronisierst um dann zum beispiel eine "running" Variable auf false zu setzen.

Ist dir verständlich warum man Threads beim Datenaustausch synchronisieren muss oder nicht? (das müsste man vieleicht etwas ausführlicher Erklären, da sich daraus ergibt warum man manchmal joinen muss)

HaWe
13.06.2019, 11:18
danke, ich vermute, genau das ist der Punkt - wieso muss ich etwas synchronisieren, wenn doch der pthread geendet hat?

schorsch_76
13.06.2019, 11:50
dankeschön, also wäre joinable die bessere Strategie...
join aber haißt doch "beitreten, verbinden" -
am Schluss will ich ihn aber doch beenden (gerade wenn er sich aufgehängt hat, um ihn dann neu zu starten):
wieso dann "join=beitreten" - mir ist das Konzept hinter join noch nicht klar.... :-k

Ja, eher zusammenführen der zwei Anläufe.

In der Regel nutze ich die C++11 threads.

Das sieht dann etwa so aus:


#include <thread>
#include <atomic>
#include <iostream>

std::atomic<bool> thread_run { true };
void execute()
{
int i = 0;
while (thread_run.load())
{
// do work
std::cout << "Hello from Thread: " << i++ << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1 ));
}
}

int main(int argc, char** argv)
{
std::thread worker(&execute);
std::this_thread::sleep_for(std::chrono::seconds(5 ));
thread_run = false;
if (worker.joinable())
{
worker.join();
}
return EXIT_SUCCESS;
}

g++ main.cpp -lpthread
georg@hammerhead /tmp ./a.out
Hello from Thread: 0
Hello from Thread: 1
Hello from Thread: 2
Hello from Thread: 3
Hello from Thread: 4

HaWe
13.06.2019, 12:15
danke, wie es geht mit pthread_join(), das weiß ich -
mir ist nur nicht klar, wieso "join" und nicht z.B. "stop" oder "kill"...?
was muss da gejoint oder gesynct werden?

schorsch_76
13.06.2019, 12:26
Der Linux Scheduler hat jetzt 2 Einträger in seiner Taskliste die er abarbeiten muß. Beim beenden muß der Worker wieder gejoint werden um das Programm sauber zu beenden. Wenn der mainthread sich beendet, ruft Linux für deinen Thread kill() auf und beendet ihn auch, nur: Du weißt nicht, in welchen Zustand er beendet wurde. Waren Dateien halb geschrieben? Das interessiert nicht mehr... Jetzt ist schluß.... kill() ;)

HaWe
13.06.2019, 12:33
hmmm.... was bedeutet "muß der Worker wieder gejoint werden um das Programm sauber zu beenden"?
Meinst du mit Worker den timeslice (roundrobin) Scheduler, der alle threads nacheinander aufruft?
und was daran muss gejoint=beigetreten oder verbunden werden?

(auf dem Screenshot erkenne ich leider nichts)

Ceos
13.06.2019, 12:37
Einfaches Beispiel, du hast einen Arbeitskollegen (Thread Objekt)

Dein Kollege arbeitet mit einem Trennschleifer an einer Platte, während du auf einen anderen Teil der Platte was anzeichnest ... solange er arbeitet kannst du ihm zurufen und winken so viel du willst aber er bekommt es nicht mit weil er in seinem Kontext ist (Gehörschutz und Schutzbrille), solltest du in seinen Arbeitsbereich kommen gibts Ärger (Concurrent Access als Fachwort)

Damit du deinem Kollegen etwas mitteilen kannst, musst du ihm erstmal auf die Schulter klopfen (join) um ihm zu sagen dass die Arbeitszeit rum ist, er kümmert sich dann darum die aktive Aufgabe abzuschließen (den rest der loop bis zum definierten Abbruch), zu fegen und zu gehen.

Du kannst dem Kollegen auch einfach den Strom abschalten oder ihn abmurksen (kill) aber dann ist die Aufgabe nicht erledigt und überall liegt noch der Staub rum

Wenn dein Kollege aber schon gegangen ist, kannst du ihm nicht auf die Schulter klopfen (weswegen ich auch nciht verstehe was du damit meinst "einem thread joinen der schon beendet ist" ... das geht eigentlich nicht und sollte einne Fehler geben)

Detached in dem Kontext wäre, wenn dein Kollege sich in einem Zimmer einschließt und du ihm nicht mehr auf die Schulter klopfen kannst aber auch nicht mehr direkt weist ob er überhaupt noch da ist!

Deswegen sind Threads auch nur ein Konstrukt, aber du musst dir halt noch Mühe machen den Thread zu steuern und die Steuerung selber implementieren! (Arbeitsanweisung als Beispiel, bei rotem Licht arbeit einstellen, aufräumen und nach Hause gehen ... wie das rote Licht in deinem Fall implementiert ist, bleibt dir überlassen)

PS: Threads haben komplexere Strukturen im Speicher als du vermuten magst und deswegen gibts es Einschränkungen miteinander zu kommunizieren ... du weist schließlich nicht was dein Kollege denkt, nur das was er sagt und macht. (Die Rote Lampe muss natürlich Thread-Safe sein ... eine Atomic Variable wie es schon oben geannt worden ist, ein Mutex der verhindert dass 2 Threads "gleichzeitig" auf die selber Variable zugreifen .. oder du klopfst dem Kollegen auf die Schulter damit du den Schalter der roten Lampe an seinem Platz anmaschen kannst ohne in sein Arbeitsbereich einzugreifen)

schorsch_76
13.06.2019, 12:39
Es gibt in dem Screenshot a.out und einen Eintrag unter a.out. Das eine ist der main thread und das zweite ist der worker thread.

HaWe
13.06.2019, 12:41
ach so, worker ist der pthread.
aber trotzdem verstehe nicht, was da "gejoint" wird bzw. was damit gemeint ist.
joinen in meinem Verständnis heit, es läuft jetzt - statt wie bisher pseudosimultan getrennt - nunmehr unter einem gemeinsamen Dach in einem gemeinsamen (main) thread weiter.

schorsch_76
13.06.2019, 12:47
Hier: Das zeigt es Graphisch

https://social.technet.microsoft.com/wiki/contents/articles/29538.c-explained-differences-between-threads-thread-pool-delegates.aspx

Der worker thread muss eben wieder sauber beendet werden und die Resourcen dem OS zurückgegeben werden. Nach dem join() ist dein Programm wieder single threaded.

HaWe
13.06.2019, 12:50
das klingt aber nach einem "stoppen und aufräumen", nicht nach "beitreten"... :confused:

Ceos
13.06.2019, 12:54
@HaWe schau bitte meinen Beitrag auf Seite 1 nochmal an, das hat sich beim tippen gerade überschnitten und ist auf Seite 1 letzter Beitrag untergegangen, da erkläre ich dir exemplarisch warum du "joinen" musst ... Threads könne nicht einfach so miteinander reden

HaWe
13.06.2019, 12:57
"beitreten" bedeutet doch, dass etwas nach wie vor weiterläuft, nur jetzt gemeinsam, nicht mehr getrennt...
(hat sich überschnitten)

- - - Aktualisiert - - -

@Ceos:
ja, das hatte ich gelesen - aber nicht verstanden.
Mir geht um die Wortbedeutung von "join", und das heißt "verbinden, beitreten", nicht "beenden".
"stoppen und aufräumen" würde ich verstehen.

schorsch_76
13.06.2019, 12:59
join bedeutet aber auch vereinigen ;)

https://dict.leo.org/englisch-deutsch/join

HaWe
13.06.2019, 13:01
ja, aber vereinen bedeutet: es läuft weiter, gemeinsam unter 1 Dach.

wenn 1 thread jede Sekunde etwas ausgibt und unabhängig ein anderer thread etwas berechnet ,
und jetzt wird gejoint (vereint),
dann machen beide alles weiter, aber nicht mehr simultan nebeneinander sondern jetzt nacheinander (aneinandergehängt).

"join the army" bedeutet ja auch: der Army beitreten und mitmachen, und nicht aus ihr austreten oder (sofort) sein Leben beenden... ;)

Ceos
13.06.2019, 14:50
Mir geht um die Wortbedeutung von "join", und das heißt "verbinden, beitreten", nicht "beenden".
"stoppen und aufräumen" würde ich verstehen.

Darum ja mein Beispiel mit dem Kollegen udn der Flex ... solange der Kollege am schleifen und schneiden ist und seinen Lärmschutz und Schutzbrille aufhat, kannst du winken, schrein, fuchteln, wedeln .... er bekommt nix mit!!!!

Du musst ihn erst auf die Schulter klopfen (join) um ihm dann zu sagen er kann Feierabend machen ... der Kollege muss sich dann aber SELBER darum kümmern aufzuräumen und nach Hause zu gehen!

Ein "kill" ist so als würdest du ihn einfach von jetzt auf gleich aus der Tür schmeißen und DU müsstest dann alles Aufräumen ... da es aber nicht dein Arbeitsplatz ist KANNST DU ES NICHT! Du kannst bestenfalls selber nach Hause gehen und vor dem verlassen noch alles anzünden und morgen von vorne anfangen.

HaWe
13.06.2019, 14:56
ja, ok, trotzdem ist das, was er machen soll, ja nicht in den main thread kommen und weitermachen mt dem, was er bisher tut (join), sondern aufhören und aufräumen (stop_and_cleanup)

Ceos
13.06.2019, 14:59
Und um hier nochmal zu erklären warum das so kompliziert sein muss:

Threads haben einen eigenen Kontext in dem sie arbeiten und in dem alle Daten als vertrauenswürdig erscheinen!
Ein Thread KANN THEORETISCH hingehen und Variablen in einem anderen Thread verändern wie es ihm beliebt ... ob der andere Thread damit klarkommt oder einfach ins leere greift, weil du die Flex auf einen anderen Platz gelegt hat ist fragwürdig und daher von den meisten Thread Bibliotheken verboten und resultiert zum Beispiel in Java in einer Exception wegen falschem Thread Zugriff!

Man kann sogenannte ATOMICS einsetzen, dabei werden Passagen des Programms innerhalb eines Threads für "nicht unterbrechbar" erklärt und niemand kann während dieser Sequenz irgendwelche Variablen verändern oder die Ausführung dieser Sequenz behindern/beenden

Man kann auch überall yield() oder sleep() einsetzen, wann immer man der Überzeugung ist, dass alle Variablen und auch der Zustand des Threads es erlauben die Kontrolle über das Programm an einen anderen Thread abzugeben, welcher dann nach belieben Variablen in dem wartenden Prozess zu verändern. Bei Microcontrollern REICHT DIESE METHODE VOLLKOMMEN AUS!!!

Bei Mehrkernsystemen kann es aber dazu kommen, dass das yield() ignoriert wird und der Thread weiterarbeitet, weil es mehr als einen echten Kern gibt! Also kann es sein, dass Thread A estwas in Thread B verändern will, der aber stur weiterarbeitet und du somit keinen Zugriff erhälst (laute Flex, eingeschränkes Gehör und Sicht) ... also musst du joinen/anklopfen ... und dann macht Thread B an einer der definierten Stellen (yield(), sleep(),...) eine ECHTE Pause und wartet dass du dein Join beendest und er weiter machen kann!

ACHTUNG WICHTIG falls du den Merhkernpart übersprungen hast weil er dich nicht trifft, ist es dennoch gute Praxis immer ein join/mutex/atomic zu nutzen um konsistente daten zwischen den Threads zu gewährleisten!

HaWe
13.06.2019, 15:12
das Prinzip, was gemacht werden kann und muss, ist mir doch klar!
Es geht mir darum, dass der Befehl "join" heißt, obwohl das nichts mit "join=komm her+mach mit" zu tun hat , wenn man sagen will:
"stop jetzt und räum auf!"

Das wäre, wie wenn eine Kolonne im Straßenbau einem Arbeiter, der einsam seine Gräben schaufelt, sagt:
"Join here = schließ dich hier an"!
was bedeutet: "komm her und mach hier bei uns mit",
und es aber tatsächlich verstanden wird als: schmeiß den Spaten auf den Laster und geh nachhause!

Da kann doch was nicht stimmen!

Ceos
13.06.2019, 15:20
Mhm ... mir fällt gerade auf dass ich die ganze "join" mit "invoke" verwechsle ... Sorry fürs irreführen!!!!

Join macht einfach aus zwei Threads wieder einen ... du rufst thread.join() in der main auf und die main wartet dann so lange bis dein Thread sich beendet hat und setzt dann die Main fort

Würdest du das nicht tun und der Main Prozess endet obwohl der Worker noch (sagen wir mal in eine Datei schreibt) würde der einfach abgewürgt und alle nicht gespeicherten Daten gehen verloren.

PS: da sieht man mal wieder was passiert wenn man Jahrelang nurnoch mit Controllern arbeitet und seit einer Weile kein echtes Multithreading mehr programmiert hat :D

Aber um das "wie beende ich den Thread" zu erklären kannst du dich gerne an meine anderen Ausführungen halten ... Threads haben einen eigenen Kontext und sehen nicht ob der andere gerade was arbeitet oder nicht. Du weist also auch nciht ob der andere verstanden hat was du von ihm willst, daher muss man sich erst synchronisieren! Oder feste Zeichen vereinbaren (eine Ampel mit Rot Grün für Arbeiten oder Feierabend ... in dem Sinne eine globale Variable)

HaWe
13.06.2019, 15:24
ich mache ja nicht aus 2 threads einen.
ich lasse einen der beiden weiterlaufen wie bisher
- und sage einem anderen, er soll aufhören und aufräumen: Denn der macht ja nicht mit in dem anderen Thread, der übrig bleibt.

Ceos
13.06.2019, 15:28
nochmal in einem Satz!

Du rufst in der main dein thread.join() auf und die Main wartet so lange bis der thread zum Ende gekommen ist! das macht man NUR DANN wenn man das ganze Programm sauber beenden möchte, oder sonst keine andere Möglichkeit hat zu überprüfen ob der Thread effektiv beendet ist oder nicht!

Du könntest auch eine "workerFinished" Variable global anlegen, im anfang der thread.loop() die Variable auf false setzen und als allerletzte Aktion diese Variable wieder auf true setzen! dann bauchst du kein join() sondern nur ein while(!workerFinished) sleep(1); Schleife

Wenn ein Thread auf einen anderen warten muss bis dieser beendet ist wäre es aber pure Zeitverschwendung jede Sekunde NUR um diese variable zu prüfen den anderen Thread zu unterbrechen (1 Kern System) also macht es mehr Sinn beide Thread durch ein join zu einem zu vereinen ... solange der thread nicht fertig ist kann die main doch soweiso ncihts sinnvolles machen!

HaWe
13.06.2019, 15:32
verstehe ich nicht, was das, was du beschreibst, mit "join=mitmachen" zu tun hat, ich nenne es "geordnet stoppen".

Ceos
13.06.2019, 18:00
ich nenne es "geordnet stoppen".

Das ist halt deine Meinung, ich sage dir nur was der Rest der Welt denkt.

Dein Wortverständnis ist wie ein Tellerrand und macnhmal sollte man darüber hinwegsehen um weiter zu kommen .. genau so wie ich häufiger detailliert die Beiträge lesen sollte um gleich zu erkennen dass es hier nur um das "koordinierte stoppen" geht

https://dict.leo.org/englisch-deutsch/join

Vielleicht gefallen dir die alternativen Übersetzungen besser

HaWe
13.06.2019, 19:06
Jap, ich dachte, es müsste hinter "join" vom Wortverständnis her etwas anderes stecken als nur "geordnetes stoppen", aber es scheint wohl dass das ist nicht der Fall ist.

Wenn das also so ist und nach join nichts weiterläuft:
Dann überlege ich gerade, was man zu tun hat, wenn ein thread hängt ( _HEARTBEAT_ = false ),
z.B. der, der UART übeträgt...


edit,
vorige Idee kann nicht funktionieren über Semaphore, dann also nach
pthread_create(&tid, ...)

zum Beenden :
pthread_testcancel(tid)
pthread_cancel(tid)

oder mit Gewalt
pthread_kill(tid, SIGKILL); // in <signal.h>
pthread_join(tid);

und dann neu starten

Ceos
13.06.2019, 19:44
DAS ist die Kunst des programmierens solche deadlocks einfach nicht zu machen


z.B. der, der UART (http://rn-wissen.de/wiki/index.php?title=UART) übeträgt...


spröde einfache antwort: blocking read vermeiden!

du musst halt auch ein wenig mehr aufpassen was du machst wenn du threads verwendest ... schonmal mit serial.available() gearbeitet?! das blockiert nicht und sagt dir wieviele bytes im puffer sind!

es gibt KAUM eine schnittstelle bei der es unmöglich ist blocking calls zu vermeiden, alles nur eine frage des aufwandes

HaWe
13.06.2019, 19:57
DAS ist die Kunst des programmierens solche deadlocks einfach nicht zu machen

spröde einfache antwort: blocking read vermeiden!

du musst halt auch ein wenig mehr aufpassen was du machst wenn du threads verwendest ... schonmal mit serial.available() gearbeitet?! das blockiert nicht und sagt dir wieviele bytes im puffer sind!

es gibt KAUM eine schnittstelle bei der es unmöglich ist blocking calls zu vermeiden, alles nur eine frage des aufwandes

das ist doch Unsinn, das kann ich nicht 100%ig vermeiden!
z.B., was ist wenn das UART-USB- Kabel abgeht oder doch wieder der kernel dazwischenfunkt und zu einem timeout führt (edit: wie vorher in der singlethread-Version)?
Oder der Arduino aus irgendeinem unbekannten Grund temporär den USB blockiert?
Genau dafür brauche ich nun eben diesen Plan B!

Ceos
14.06.2019, 11:04
Wenn man richtig Bare Metal programmiert ist das möglich, Wenn man Arduino nutzt muss man halt mit den Schwächen leben oder sich intensiver damit befassen und einen Workaround entwickeln oder finden!


was ist wenn das UART-USB- Kabel abgeht

Das kann man Problemlos erkennen, wenn man Kontrolle über den USB Stack hat bzw. weis wie die Peripherie des Chip arbeitet


oder doch wieder der kernel dazwischenfunkt

was hat das bitte mit read() und available() auf Controllerseite zu tun, das ist abwegig! Oder du meinst etwas anderes...


und zu einem timeout führt (edit: wie vorher in der singlethread-Version)?

wenn available == 0 dann sperrt read oder bekommt ein timeout wenn man das einstellen kann (hab ich gerade keine ahung unter arduino was das angeht)
wenn available > 0 dann sperrt read 100% nicht!


Oder der Arduino aus irgendeinem unbekannten Grund temporär den USB (http://www.rn-wissen.de/index.php/USB) blockiert?

Das ist ein ARduino Problem und der einzige Weg wäre Arduino nicht zu nutzen!

Ich hab doch im ersten Satz geschrieben das es die Kunst des Programmierens ist ... Wenn ich einen Vergleich zwischen Arduino und echtem Bare Metal programmieren ziehen müsste, würde ich sagen, Bare Metal ist mit Pinsel und Leinwand während Arduino Photoshop und einen Drucker sind.
Dein Photoshop(Arduino) kann hängen bleiben und dein Drucker(Board Lib für Arduino) kann abstürzen oder falsch arbeiten.
Beim Zeichnen mit Pinsel und Leinwand entscheidet nur die Erfahrung, Übung und die Qualität der Leinwand(Controller/Board) über das Ergebnis.

Wenn man Bibliotheken nutzt, muss man sich mit den Schwächen abfinden oder eben selber das Konzept verstehen und entweder den Fhler an der Lib beheben oder selber eine schreiben.

Aber da rutschen wir gerade in eine Grundsatzdiskussion ab :) also lassen wir das Thema lieber, ich wollte dir nur klarmachen dass es absolut vermeidbar ist wenn man auf Komfort verzichtet. (ohne genauer auszuführen was ich mit Konfort meine)

HaWe
14.06.2019, 11:16
es geht um Raspi code, nicht um Arduino-Code!
SerialAvailable() aus der wiringPi lib arbeitet nicht zuverlässig, um solche Fehler eindeutig zu lokalisieren, und auch timeouts tun es nicht, das habe ich schon mit diversen Tests (aus dem Raspi C++ Unterforum) herausgefunden. und wenn Raspi und Arduino im Robot verbaut sind und der Robot herumfährt und folglich der direkten Beobachtung entzogen ist, kann man den Grund für eine Verbindungsunterbrechung nicht unbedingt erkennen.
Es geht hier mit dem Beenden und Neustart eines Threads also lediglich um den automatisierten Versuch des autonomen Programms, den momentanen Fehler (z.B. kein Thread-heartbeat mehr) mit Bordmitteln selber zu händeln.
Einer davon soll sein:
den hängenden Thread stoppen (schrittweise: erst pthread_exit(), wenn erfolglos: pthread_kill(), dann das Serial file zu schließen, Variablen zu resetten, und dann das Serial file neu zu öffnen und den Thread neu zu starten.

Erfahrungsgemäß treten solche Fehler ab und an nicht nur bei UART sondern auch bei i2c auf, auch dafür soll eine solche Notfallmaßnahme implementiert werden.

Ceos
14.06.2019, 11:29
Ach verdammt schon wieder mental verrutscht ... Ich war schon wieder bei Arduino Code sorry :(

Aber das macht es ja sogar noch einfacher, da der Kernel eigentlich alle Funktionen bereitstellt um auch das USB-Detach zu erkennen ... dass der Kernel beim read dazwischen Funkt und man das nicht erkennen kann fällt für mich unter die Kategorie "Kernel Bug" und damit muss man halt leben.

Aber dass die WiringPi Lib notorisch Buggy ist sollte bekannt sein. Es wird ja kontinuierlich dran gebastelt von vielen Köchen (dazu habe ich schonmal was gesagt)

Wenn du statt der Wiring Pi direkt die Kernelfunktionen benutzt hast du auch hier die volle Kontrolle!

UART lässt sich auch ganz bequem per open("/dev/ttyXXXX") erledigen aber dazu müsstest du dich ein wenig tiefer in Linux versetzen

In Lnux wird jede Peripherie und USB Gerät durch den entsprechenden Kernel-Treiber in das Dateisystem projeziert. Es ist die Qualität des Treibers die entscheidet was für Funktionen man nutzen kann, ähnlich wie es die Entscheidung des Hersteller ist wie eine Peripherie in dem entsprechenden Controller integriert wird.

Das was bei Linux das DAteisystem ist, sind die Steuerregister im Controller und solange man die beherrscht und auch der TReiber die Funktion hergibt, kann man auch hier die volle Kontrolle bekommen.

HaWe
14.06.2019, 11:37
ich habe das Gefühl, du argumentierst sehr ins Blaue - hast du jemals selber auf dem Raspi UART mit wiringPi programmiert?
Nur von wiringPi gibt es ja die serialAvailable Funktion.
Was weißt du sonst über die Funktionsweise der wiringSerial Funktionen?

Dass ich aber weiß, wie man UART nutzt, das kannst du schon vorraussetzen, wie du auch problemlos in meinem Arduino-Raspi-UART code nachgucken kannst (https://www.roboternetz.de/community/threads/73470-UART-Kommunikation-zwischen-Raspi-und-Arduino-h%C3%A4ngt-sich-st%C3%A4ndig-auf?p=652616&viewfull=1#post652616) - der läuft ja nun unter Normalbedingungen zusammen mit den kernel Programmen stabil ;)
(ich kann nur nicht garantieren, dass IMMER der kernel nur das tut, was ich vorhersehen kann)

- - - Aktualisiert - - -

PS,

Aber dass die WiringPi Lib notorisch Buggy ist sollte bekannt sein. Es wird ja kontinuierlich dran gebastelt von vielen Köchen (dazu habe ich schonmal was gesagt)
an der WiringPi Lib aber arbeitet nur Gordon Henderson, selbst da irrst du also - es wird über das apt package oder direkt von Gordon's git site bereitgestellt.


Bitte halte dich also ans Topic, und da geht es hier ausschließlich um pthread.

Ceos
14.06.2019, 11:40
Einer davon soll sein:
den hängenden Thread stoppen (schrittweise: erst pthread_exit(), wenn erfolglos: pthread_kill(), dann das Serial file zu schließen, Variablen zu resetten, und dann das Serial file neu zu öffnen und den Thread neu zu starten.

Um Threads zu steuern und zu Überwachen gibt es viele Möglichkeiten (besonders beim Mehrkern System):

Join ist die simpelste und ist eigentlich nur Sinnvoll, wenn man kurzlebige Threads hat, welche etwas parallel bearbeiten sollen udn dann ERgebnisse zurücklieefern sollen ... der Master kreaiert 10 Threads die unabhängig rechnen, wärend der Master noch irgendwas erledigt. Damit man dann synchronisieren kann, wendet man dann nacheinander auf alle Worker ein Join an um den Thread mit der Main zu synchronisieren, endet der Thread, fährt der Master mit den nächsten Join fort oder was auch immer er zu tun hat. (Das birgt den Nachteil dass man garantieren muss, dass der Thread niemals in ein Dead Lock geraten kann oder diese Methode ist unbrauchbar!)


Die Methode die für dich sinvoller erscheint sind Steuersignale und eine abbrechbare Thread Schleife ohne irgendwelche Hilfmittel von Thread-Librarys! (Thread librarys mögen verschiedene hilfereiche Funktionen anbieten, aber bisher war das immer mit einem unsauberen Exit behaftet oder mit entsprechend umständlichem try-final Blöcken bzw. einem Custom-Destructor)
Deine Threadschleife braucht 2 Abbruchflags von außen und 1 Zustandsflag nach drausßen. Das erste Abbruchsflag sollte in deinem Code dazu führen dass alles abgeschlossen wird und die Eegebnisse zurück geliefert werden und anschließend über das Zustandsflag signalisieren dass der Thread quasi beendet ist (in etwa wie ich es in dem Beispiel weiter oben beschrieben habe, ein Join nach dem pollen eines "Thread beendet" Signals ist immernoch sinnvoll um Raceing Conditions zu vermeiden, sollte aber i.d.R. nicht länger als ein paar Takte blockieren!)

Es gibt Thread Librarys die bieten entsprechende Abort-Funktionen an, die muss man dann im Thread Objekt selbst überschreiben oder eine Methode anbinden/implementieren, das macht es etwas übersichtlicher!

Aber wenn dein Thread einfach nicht "Thread beendet" zurück meldet musst du dir selbst ein kleines Timeout einbauen und den Thread halt mit Kill beenden!

Join sollte man nur nutzen wenn man auch garnatieren kann dass der Thread in absehbarer Zeit beendet wird oder das Programm hängt sich auf :D

PS:
hast du jemals selber auf dem Raspi UART (http://rn-wissen.de/wiki/index.php?title=UART) mit wiringPi programmiert?
Nur von wiringPi gibt es ja die serialAvailable Funktion.
Was weißt du sonst über die Funktionsweise der wiringSerial Funktionen?

Ja, habe ich ... nutze ich nur nicht gerne weil eben verbuggt aber serial per kernel ist halt auch eine scheiß arbeit.

Ich weis auch so prinzipiell wie es arbeitet, der Code steht auf Github da kann man nachlesen und auch Reports machen wenn einem ein Bug auffällt

Das entscheidende ist dass du noch nicht weist wie Linux arbeitet und deshalb nicht das große ganz und die alternativen siehst .. du argumentiert es sei "unmöglich" exakt formuliert wäre da besseer "es ist für DICH und IM MOMENT unmöglich"

Kunst kommt vom Können und das kann man lernen

HaWe
14.06.2019, 11:53
wieder mal sehr theoretisch -
poste lieber mal einen funktionierenden Code, um meinen hängenden pthread mit UART zu stoppen und neu zu starten, aller andere theoretische Krimskrams ist mir ja klar.

wenn der pthread aber komplett hängt, dann empfängt er auch von außen keine Variablen mehr bzw. kann auch nicht auf Semaphore oder interne Variablen reagieren.
Er hängt dann fest wie wenn da nur stünde

while(1);

- - - Aktualisiert - - -
PS,

klar weiß ich, wie man mit Linux programmiert, und Details, die ich nocht weiß, die frage ich nach.
So wie jetzt, aber wie es grob geht weiß ich ja auch - siehe hier:
https://www.roboternetz.de/community/threads/73509-pthread-was-genau-macht-joinable-und-was-macht-detached?p=652792&viewfull=1#post652792

- - - Aktualisiert - - -

PS 2:

der Code auf github ist nicht von Gordon sondern inoffiziell von einer dritten Person, was aber von Gordon nicht supportet und auch nicht registriert wird.

Ceos
14.06.2019, 12:02
wieder mal sehr theoretsch -

dein Mantra ... mein Mantra kennst du ja, animiere zu denken aber vorgekaut wird nur gegen Bargeld


poste lieber mal einen funktionierenden Code, um meinen hängenden pthread mit UART (http://rn-wissen.de/wiki/index.php?title=UART) zu stoppen und neu zu starten


Folgender Code sollte funktionieren, solange du niemals detach aufrufst, sonst musst du dir den native-Handle geben lassen und den Thread mit OS Funktionen abschießen



thread::~thread();

und dein Thread ist beendet! Ausprobiert habe ich es nicht, sondern nur eine Random Thread Doku aus dem Netz gezogen, weil ich üblicherweise den Thread Locksafe schreibe und über new erzeuge und auch selber wieder aufräume.

Ein Thread ist immernoch ein Objekt und wenn du den Destruktor aufrufst wird es beendet und zerlegt.

Wenn du aber Detach aufgerufen hast, hast du keinen Zugriff mehr auf das Objekt und musst es vom OS aus killen.

Danach kannst du den Thread einfach neu erstellen(in deinem Fall etwas problematisch denke ich da er statisch angelegt ist).

Wenn dein Thread bekannterweise instabil und DeadLock lastig ist, solltest du ihn vielleicht nicht statisch sondern über new und Objektpointer erzeugen! (Kann mich gerade nicht erinnern was davon "auf dem heap" und was davon "auf dem stack" angelegt wird XD aber ich hoffe du verstehst was ich meine)

PS: https://github.com/WiringPi/WiringPi auch wenns offiziell inoffiziell ist, gerade WENN das original nicht public ist (diese Fassung wird aber gut aktualisiert) würde ich es nicht nutzen, aber ich habe wie gesagt meine ERfahrungen gemacht und verzichte einfach drauf

HaWe
14.06.2019, 12:05
bitte mal echten Code, keine Allgemeinplätze!
auch arbeite ich nicht mit std::thread sondern pthread !!! (Bitte immer genau lesen!)

den aktuellen Code findest du hier: https://www.roboternetz.de/community/threads/73470-UART-Kommunikation-zwischen-Raspi-und-Arduino-h%C3%A4ngt-sich-st%C3%A4ndig-auf?p=652711&viewfull=1#post652711
(habe ich frisch geupdated)

Ceos
14.06.2019, 12:14
int pthread_kill(pthread_t thread, int sig);

+

Signal x86/ARM Alpha/ MIPS PARISC Notes
most others SPARC
────────────────────────────────────────────────── ───────────────
SIGKILL 9 9 9 9
SIGSTOP 19 17 23 24

das ist gleichzusetzen mit dem was du vom OS aus machen kannst! Damit ist der Prozess 100% weg und kann neu gestartet werden. Dein Bedürftnis nach fertig compilierebarem Code kann ich nicht befriedigen, dafür ist mir die Bezahlung zu schlecht.

PS sigkill ist das signal der WAhl, was sigstop macht habe ich selbst so noch nie getestet, aber da es nciht Platformunabhängig ist würde ich es auch ncie in Betracht ziehen

HaWe
14.06.2019, 12:21
SIGKILL habe ich doch oben als Parameter für pthread_kill(tid, sig) selber schon genannt
https://www.roboternetz.de/community/threads/73509-pthread-was-genau-macht-joinable-und-was-macht-detached?p=652792&viewfull=1#post652792
- es ist der höchstmögliche, "stärkste" Wert für Linux - inwiefern soll dein Hinweis jetzt weiterhelfen?
(edit: SIGTERM oder SIGQUIT gingen auch, aber ich will ja für den Notfall die "stärkste" Methode, die mit der höchsten Wahrscheinlichkeit sofort wirkt).

Ceos
14.06.2019, 12:33
die mit der höchsten Wahrscheinlichkeit sofort wirkt

das IST "sigkill"

es gibts nichts "höheres" in dem Sinne und es garantiert auch dass der Prozess danach weg ist, sonst wäre das ein linux bug

(oder du hast detach aufgerufen)

HaWe
14.06.2019, 12:44
das IST "sigkill"

es gibts nichts "höheres" in dem Sinne und es garantiert auch dass der Prozess danach weg ist, sonst wäre das ein linux bug

(oder du hast detach aufgerufen)

habe ich irgend etwas anderes geschrieben?

- - - Aktualisiert - - -

PS:
die Frage war nach einen verwertbaren Code, zum beenden UND sicher neu starten

Ceos
14.06.2019, 12:51
viel erfolg bei der suche

schorsch_76
17.06.2019, 08:33
Um so etwas komplett "abschiessen" zu können wäre ein Child Process [3][4] möglich. Damit könntest du das komplett abschießen und neu starten (Forken). Die Kommunikation würde dann über bsw. Fifo [2] laufen.

Ich empfehle dir das Buch: "The Linux Programming Interface: A Linux and UNIX System Programming Handbook"

Leider geht es nicht ohne Theorie. Wenn die Praxis nicht zur Theorie passt, läuft alles total schief und führt zu den verschiedensten Problemen. Computer verhalten sich nun mal nur logisch nach den definierten API's. Bsw. Posix.

[1] ISBN-13: 978-1593272203: The Linux Programming Interface: A Linux and UNIX System Programming Handbook
[2] https://linux.die.net/man/4/fifo
[3] https://linux.die.net/man/2/fork
[4] http://man7.org/linux/man-pages/man2/kill.2.html

HaWe
17.06.2019, 09:31
Um so etwas komplett "abschiessen" zu können wäre ein Child Process [3][4] möglich. Damit könntest du das komplett abschießen und neu starten (Forken). Die Kommunikation würde dann über bsw. Fifo [2] laufen.

Ich empfehle dir das Buch: "The Linux Programming Interface: A Linux and UNIX System Programming Handbook"

Leider geht es nicht ohne Theorie. Wenn die Praxis nicht zur Theorie passt, läuft alles total schief und führt zu den verschiedensten Problemen. Computer verhalten sich nun mal nur logisch nach den definierten API's. Bsw. Posix.

[1] ISBN-13: 978-1593272203: The Linux Programming Interface: A Linux and UNIX System Programming Handbook
[2] https://linux.die.net/man/4/fifo
[3] https://linux.die.net/man/2/fork
[4] http://man7.org/linux/man-pages/man2/kill.2.html

Leider auch nur sehr theoretisch, die man pages kenne ich überwiegend schon; außerdem arbeite ich mit wiringPi und seinen file wrappern und seinen handles/fd's, nicht mit nativen files.
Was ich inzwischen aber heraus bekommen habe:
wenn ein realtime thread mit SCHED_RR prio 40 oder 50 hängt (Bereich: 0-99), komme ich auf einem single core Raspi (B+, Zero) überhaupt nicht mehr an irgend etwas heran: das gesamte Programm blockiert dann vollständig.
Auf einem multicore (2B, 3B) geht das schon besser, dann muss es aber wschl von einem höherwertigen thread aus passieren.
Unklar ist, wie SCHED_FIFO / _OTHER etc threads vom Scheduler behandelt werden gegenüber dem hängenden thread, und von einer main() loop aus wird es möglichereise auch nicht gehen, wenn weitere high prio threads laufen, denn (edit) die main loop läuft mit nice=0 (+20...-19, vergleichbar mit SCHED_OTHER (by default), hier gibt es keine prios), und kein Mensch weiß offensichtlich, wie die prios und nices untereinander verrechnet werden ).
Es wird also per zusätzlichem "watcher_thread" laufen müssen, d.h. von einer höheren SCHED_RR prio aus, und es dürfen wschl KEINE SCHED_FIFO threads laufen, wozu du verlinkt hast.

Er muss dann also über einen Thread mit SCHED_RR prio >50 laufen, der alle anderen überwacht (edit: z.B. prio 70-90, aber langen delays/yields zwischendurch),
von dort den anderen mit thread_kill() abbrechen,
dann UART (fd=Serial) beenden,
dann zusätzlich joinen, damit der threadID Handle gelöscht wird,
alles an Variablen /Semaphoren zurücksetzen,
dann UART (fd=Serial) sicher neu starten (geht das?),
dann den vorher abgebrochenen Thread neu starten (geht das mit dem früheren fd=Serial und der früheren threadID, jetzt neu zu vergeben?)
und dann muss die UART Verbindung sich neu mit dem Arduino re-syncen (geht das, auch wenn zwischendurch der virtuelle USB COM Port weg war?)

Ich vermute, viele wird es hier nicht geben, die sich mit der Materie tatsächlich auskennen und einen sicheren Beispielcode posten können, auch wenn ich es gehofft hatte...

schorsch_76
17.06.2019, 09:49
...
Ich vermute, viele wird es hier nicht geben, die sich mit der Materie tatsächlich auskennen und einen sicheren Beispielcode posten können, auch wenn ich es gehofft hatte...

HaWe: Ich mache das seit 20 Jahren in C++. Jeden Tag. Hier zeige ich dir die Richtung die gangbar ist, aber ich werde dir nicht dein Projekt schreiben.

HaWe
17.06.2019, 09:51
HaWe: Ich mache das seit 20 Jahren in C++. Jeden Tag. Hier zeige ich dir die Richtung die gangbar ist, aber ich werde dir nicht dein Projekt schreiben.

a ja, danke, aber die "Richtung" ist mir doch klar...
(gedacht war es ja für das Community Projekt RP6-Nachfolger, da wäre also Mitarbeit mit "echtem Code" auch für alle anderen sehr nützlich...)

(PS, edit: irgendwie "hinwurschteln" kann ich es ntl, die Frage ist nur, wie man es richtig und möglichst failsafe macht)

schorsch_76
17.06.2019, 14:39
Ich kann heut Abend ein kleines Sample schreiben ;)

EDIT:
Das mit dem Subprozess kann sicher auch nach einem wegbrechen des ttys wieder starten. Wenn der tty einfach nicht mehr zu öffnen ist, dann wird auch das nichts werden.

Um das Arduino Protokol wieder zu syncen, muss halt das Protokoll "syncbar" sein. Der Arduino hat ja nichts davon mitbekommen.

Wie schaut denn das Protokoll aus?

HaWe
17.06.2019, 16:29
Ich kann heut Abend ein kleines Sample schreiben ;)

EDIT:
Das mit dem Subprozess kann sicher auch nach einem wegbrechen des ttys wieder starten. Wenn der tty einfach nicht mehr zu öffnen ist, dann wird auch das nichts werden.

Um das Arduino Protokol wieder zu syncen, muss halt das Protokoll "syncbar" sein. Der Arduino hat ja nichts davon mitbekommen.

Wie schaut denn das Protokoll aus?

dankeschön!
das Arduino-Prog iat hier: https://www.roboternetz.de/community/threads/73470-UART-Kommunikation-zwischen-Raspi-und-Arduino-h%C3%A4ngt-sich-st%C3%A4ndig-auf?p=652616&viewfull=1#post652616

- - - Aktualisiert - - -


dankeschön!
das Arduino-Prog iat hier: https://www.roboternetz.de/community/threads/73470-UART-Kommunikation-zwischen-Raspi-und-Arduino-h%C3%A4ngt-sich-st%C3%A4ndig-auf?p=652616&viewfull=1#post652616


meine grobe Idee war (der heart beat ist noch nicht ausformuliert, nur der thread kill):


void* WATCHER_thr(void*) // very high priority: thread watcher
{
while(_TASKS_ACTIVE_) {

// to do: UART_HEARTBEAT auswerten!

if (!UART_HEARTBEAT) { // delays sind nur grobe Ideen, als yield für andere threads!
pthread_kill(thread2, SIGKILL); // does not erase handle!
delay(10); // zusätzlich weitere Wartebedingung für kill?

pthread_join(thread2, NULL); // join, erase handle
delay(10);

serialClose( Serial); // close UART, fd=Serial
delay(10);
Serial = serialOpen (uart, UARTclock); // re-open Serial
delay(1);

pthread_create(&thread2, NULL, UART_thr, NULL); // restart UART by medium
param.sched_priority = 40;
pthread_setschedparam(thread2, SCHED_RR, &param);

delay(10);
}

delay(100); // long yield

}
return NULL;
}

// andere treads siehe Link oben!

//================================================== ================
//================================================== ================

int main() {

// threads
pthread_t thread0, thread1, thread2;
struct sched_param param;

printf("starting ..."); printf("\n");

// open UART Serial com port
Serial = serialOpen (uart, UARTclock);


// start multithreading
pthread_create(&thread0, NULL, WATCHER_thr, NULL); // very high priority: thread watcher
param.sched_priority = 80;
pthread_setschedparam(thread0, SCHED_RR, &param);

pthread_create(&thread1, NULL, KBD_thr, NULL); // low priority: keyboard monitor
param.sched_priority = 40;
pthread_setschedparam(thread1, SCHED_RR, &param);

pthread_create(&thread2, NULL, UART_thr, NULL); // medium priority: UART
param.sched_priority = 40;
pthread_setschedparam(thread2, SCHED_RR, &param);


while(_TASKS_ACTIVE_) { delay(10); }

// wait for threads to join before exiting
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
pthread_join(thread3, NULL);

serialClose( Serial);
exit(0);
}

schorsch_76
17.06.2019, 19:09
Hier ist mein fork() Sample. Es forkt und überwacht einen Subprozess. Ich habe auch einen tty_dummy für den string gebastelt der dein Protokoll nachahmt bzw. parst. Er sollte so auch wieder auf syncronisieren können (tty_dummy). Der Trick hier ist "find('\n')" und wenn der regex nicht matcht, das wegzuwerfen.

Die Funktion die im Subprozess aufgerufen wird, öffnet dann den tty und spricht mit dem Master nur über die pipes. Die tty_dummy() ist nur ein ungetestetes Skelett. Auf Arduino Seite würde ich das auch so ähnlich machen.

Das ganze wurde auf meinem Debianrechner getestet.

Das Testprogram zeigt:


./restarttest
Started worker: 5433
Slave got command: 14
Got data from worker 4
Slave got command: 11
Got data from worker 3
Slave got command: 11
Got data from worker 3
Slave got command: 11
Got data from worker 3
Slave got command: 11
Got data from worker 2
Slave got command: 13
worker abort
Terminate worker: 5433
Started worker: 5434
Slave got command: 12
Got data from worker 1
Slave got command: 13
Got data from worker 1
Slave got command: 16
Got data from worker 3
Slave got command: 16
Got data from worker 3
Slave got command: 16
...




/************************************************** ****************************
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004


Copyright (C) 2019 Georg Gast <georg@schorsch-tech.de>

Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.


DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

0. You just DO WHAT THE FUCK YOU WANT TO.
************************************************** ****************************/

/************************************************** ****************************
Sample for Roboternetz.de:
pthread: was genau macht "joinable" und was macht "detached"?
https://www.roboternetz.de/community/threads/73509-pthread-was-genau-macht-joinable-und-was-macht-detached


Compile with:
-------------
g++ --std=c++11 restart.cpp -o restart

Explanation:
------------
This program creates a master process and forks a child process. These
two processes are connected by pipes. Just use plain POSIX.


+--------------------+
| Master |
+--------------------+
| ^
v |
+--------------------+
| worker |
+--------------------+


They exchange random integer bytes.


The master sends to the child random commands. If it does not get a response
within 100 millisec it sends SIGKILL to the worker that it can not prevent
from termination. After the worker is reaped, close the old pipes, create
new pipes and forks a new worker.

The worker starts to sleep, if its rng generates a 6, if it gets a 7
it calls std::abort(). In any other case it sends it to the master.
but just if SIMULATE_FAIL is 1.


The child rng uses this distribution scheme:
std::normal_distribution<> dis(3,2);
-2
-1 *
0 ***
1 *****
2 ********
3 **********
4 ********
5 ******
6 ***
7 *
8


Comments:
---------
If we need to send more data than PIPE_BUF (linux 64 bit 64kb, 32bit 4kb)
through the pipes, we need to use select(),poll() or epoll() and create a
multiplexed application then we could make sure the while loop in the
read_loop/write_loop can be avoided. It would also make an application
more expandable.

see: man 7 pipe

boost::asio would also be a nice solution to this problem and make it
platform independent.

The regex in the tty_dummy could be optimized to detect automaticly how
many ix=xxx are received.
************************************************** ****************************/

// if SIMULATE_FAIL is 1, the child simulates random failing
// if SIMULATE_FAIL is 0, the child should run forever
#define SIMULATE_FAIL 1

// std c++
#include <cassert>
#include <chrono> // clock
#include <cmath> // std::round
#include <functional>
#include <iostream>
#include <memory> // shared_ptr for FDHandle
#include <random>
#include <regex>
#include <string>
#include <thread> // sleep_for
#include <tuple>

// abort and runtime_error
#include <exception>
#include <stdexcept>

// linux + posix
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>

#include <fcntl.h>
#include <limits.h> // PIPE_BUF
#include <signal.h>
#include <unistd.h>

// make sure we always close the fd (we MUST ensure
// no fd leak as the process is very long running)
using FDHandle = std::shared_ptr<void>;
FDHandle MakeFDHandle(int fd)
{
return FDHandle(reinterpret_cast<void *>(fd), [fd](void *) {
if (fd >= 0)
{
close(fd);
}
});
}
inline int GetFD(FDHandle handle)
{
size_t p = reinterpret_cast<size_t>(handle.get());

return static_cast<int>(p);
}

// chrono helper
using clk = std::chrono::system_clock;
using timepoint_t = clk::time_point;

inline timepoint_t now() { return clk::now(); }

// blocking write until we wrote all data
size_t write_loop(FDHandle fd, const void *buffer, size_t len)
{
assert(fd.get() != nullptr && GetFD(fd) >= 0);
assert(buffer != nullptr);
assert(len > 0);
assert(len < PIPE_BUF &&
"Because in case we got a signal and interrupt the write, we must "
"be able to get the data into the pipe");

ssize_t wrote_bytes = 0;
size_t total_wrote = 0;

while (total_wrote < len)
{
int f = GetFD(fd);
while ((wrote_bytes =
write(GetFD(fd),
reinterpret_cast<const char *>(buffer) + total_wrote,
len - total_wrote)) < 0 &&
errno == EINTR)
;

// EOF
if (wrote_bytes == 0)
{
throw std::runtime_error("write_loop EOF");
}
else if (wrote_bytes < 0)
{
perror("write()");
throw std::runtime_error("write() failed");
}

total_wrote += wrote_bytes;
}
assert(total_wrote == len);

return total_wrote;
}

// blocking read until we get some data
int read_loop(FDHandle fd, void *buffer, size_t len)
{
assert(fd.get() != nullptr && GetFD(fd) >= 0);
assert(buffer != nullptr);
assert(len > 0);

int read_bytes;
while ((read_bytes = read(GetFD(fd), buffer, len)) < 0 && errno == EINTR)
;

// EOF
if (read_bytes == 0)
{
throw std::runtime_error("read_loop EOF");
}
else if (read_bytes < 0)
{
perror("read()");
throw std::runtime_error("read() failed");
}
assert(read_bytes > 0);

return read_bytes;
}

// peek into the pipe and get the available bytes
int get_avail_bytes(FDHandle fd)
{
assert(fd.get() != nullptr && GetFD(fd) >= 0);

int avail_bytes = 0;
if (ioctl(GetFD(fd), FIONREAD, &avail_bytes) != 0)
{
perror("ioctl()");
throw std::runtime_error("ioctl() failed");
}
return avail_bytes;
}

// start the worker and give back the FDHandles to and from the worker
std::tuple<pid_t, FDHandle /* rfd */, FDHandle /* wfd*/>
start_worker(std::function<int(FDHandle rfd, FDHandle wfd)> fo)
{
assert(static_cast<bool>(fo) == true && "Function object must be valid");

int master_to_worker[2];
int worker_to_master[2];
if (pipe(master_to_worker) != 0)
{
perror("pipe() master_to_worker");
exit(EXIT_FAILURE);
}
if (pipe(worker_to_master) != 0)
{
perror("pipe() worker_to_master");
exit(EXIT_FAILURE);
}

pid_t pid = fork();
switch (pid)
{
// fork failed:
case -1:
perror("fork()");
exit(EXIT_FAILURE);

// child
case 0:
{
// as the child we should close the not needed fds
close(master_to_worker[1]);
close(worker_to_master[0]);

exit(fo(MakeFDHandle(master_to_worker[0]),
MakeFDHandle(worker_to_master[1])));
}

// master
default:
{
// as a master we should close the not needed fds
close(master_to_worker[0]);
close(worker_to_master[1]);

return std::make_tuple(pid, MakeFDHandle(worker_to_master[0]),
MakeFDHandle(master_to_worker[1]));
}
}

// just to silence compiler warning: we never reach this point
return std::make_tuple(pid, MakeFDHandle(-1), MakeFDHandle(-1));
}

void kill_worker(pid_t &worker)
{
if (worker <= 0)
{
return;
}

// SIGKILL _CAN NOT_ be prohibited by the worker. It kills in any case
// the worker (if we have the right to send it this signal)
std::cout << "Terminate worker: " << worker << std::endl;
if (kill(worker, SIGKILL) != 0)
{
perror("kill()");
exit(EXIT_FAILURE);
}

// Just wait for this worker and reap the child process (prevent zombie
// process)
if (waitpid(worker, NULL, 0) != worker)
{
perror("waitpid()");
exit(EXIT_FAILURE);
}

worker = 0;
}

// signal handler for master
// here we can just use very limited number of functions
// see man signal
int selfpipe[2];
void onsignal(int signal)
{
switch (signal)
{
case SIGTERM:
case SIGQUIT:
write(selfpipe[1], " ", 1);
break;

case SIGCHLD:
break;
default:
break;
}
}

// master function. It should not fail or hang.
int master(std::function<int(FDHandle rfd, FDHandle wfd)> worker_fo)
{
assert(static_cast<bool>(worker_fo) == true &&
"Function object must be valid");

// install sighandler for SIGTERM, SIGQUIT, SIGCHLD
// with self pipe trick.
if (pipe2(selfpipe, O_NONBLOCK) != 0)
{
perror("pipe() selfpipe");
exit(EXIT_FAILURE);
}
auto rd_selfpipe = MakeFDHandle(selfpipe[0]);
auto wr_selfpipe = MakeFDHandle(selfpipe[1]);
signal(SIGCHLD, &onsignal);
signal(SIGTERM, &onsignal);
signal(SIGQUIT, &onsignal);

// make sure master and worker dont share the same rng sequence
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(11, 16);

auto last_bytes_read_at = now();
pid_t worker = 0;

FDHandle rfd;
FDHandle wfd;

bool run = true;
while (run)
{
// start the worker
if (worker == 0)
{
std::tie(worker, rfd, wfd) = start_worker(worker_fo);
last_bytes_read_at = now();
std::cout << "Started worker: " << worker << std::endl;
}

// generate a pseudo command
int cmd = dis(gen);
int wrote_bytes = write_loop(wfd, &cmd, sizeof(int));
assert(wrote_bytes == sizeof(int));

// check if the worker has wrote some data
int avail_bytes = 0;
while (avail_bytes == 0 &&
now() - last_bytes_read_at < std::chrono::milliseconds(100))
{
avail_bytes = get_avail_bytes(rfd);
if (avail_bytes == 0)
{
std::this_thread::sleep_for(std::chrono::milliseco nds(10));
}
}

// check if worker failed
if (avail_bytes == 0)
{
kill_worker(worker);
continue;
}

// got data from the worker
int data;
int read_bytes = read_loop(rfd, &data, sizeof(int));
assert(read_bytes > 0);

std::cout << "Got data from worker " << data << std::endl;
last_bytes_read_at = now();

// check self pipe and reset run flag
if (get_avail_bytes(rd_selfpipe) > 0)
{
run = false;
}
}

// kill the worker, the pipes get closed by the handles
kill_worker(worker);

return EXIT_SUCCESS;
}

int failing_worker(FDHandle rfd, FDHandle wfd)
{
// worker dont get a signal handler as we want to manipulate it by the user
assert(rfd.get() != nullptr && GetFD(rfd) >= 0);
assert(wfd.get() != nullptr && GetFD(wfd) >= 0);

// make sure master and worker dont share the same sequence
std::random_device rd;
std::mt19937 gen(rd());
std::normal_distribution<> dis(3, 2);

/*
-2
-1 *
0 ***
1 *****
2 ********
3 **********
4 ********
5 ******
6 ***
7 *
8
*/
while (1)
{
// read our command from the master
int cmd;
int read_bytes = read_loop(rfd, &cmd, sizeof(cmd));

assert(read_bytes > 0);
std::cout << "Slave got command: " << cmd << std::endl;

// send a response to the master (or sleep or abort)
int rnd = std::round(dis(gen));
#if SIMULATE_FAIL
if (rnd == 6)
{
// sleep forever: simulate hang
std::cout << "worker sleeping" << std::endl;
std::this_thread::sleep_until(timepoint_t::max());
}
else if (rnd == 7)
{
// just die: abnormal program termination
std::cout << "worker abort" << std::endl;
std::abort();
}
#endif

// simulate a read from uart and give it to master
int wrote = write_loop(wfd, &rnd, sizeof(rnd));
assert(wrote > 0);
}

return EXIT_FAILURE;
}

// an example of the tty receiver on the pi
int tty_dummy(FDHandle rfd, FDHandle wfd)
{
assert(rfd.get() != nullptr && GetFD(rfd) >= 0);
assert(wfd.get() != nullptr && GetFD(wfd) >= 0);

std::string rcv_buffer;
while (1)
{
// read data from the master
std::array<char, 256> tmp;
const int read_bytes = read_loop(rfd, &tmp[0], tmp.size());
for (int i = 0; i < read_bytes; ++i)
{
rcv_buffer += tmp[i];
}

// is there a /n
auto pos = rcv_buffer.find('\n');
if (pos != std::string::npos)
{
std::string line(rcv_buffer.begin(), rcv_buffer.begin() + pos);

// remove the line from the rcv buffer
rcv_buffer.erase(rcv_buffer.begin(), rcv_buffer.begin() + pos + 1);

// math this:
// §&i0=17628;&i1=1;&i2=17434;&i3=33;&i4=-444;&i5=5555;&i6=43690;
std::regex ex(
"^§&i0=([\\-0-9]++);&i1=([\\-0-9]+);&i2=([\\-0-9]+);&i3=([\\-0-"
"9]+);&i4=([\\-0-9]+);&i5=([\\-0-9]+);&i6=([\\-0-9]+);$");
std::smatch match;
if (!std::regex_match(line, match, ex))
{
std::cerr << "Unmatched line: " << line << std::endl;
continue;
}

std::cout << "Matched: ";
for (int i = 0; i < 7; ++i)
{
std::cout << "i" << i << "=" << match[i].str() << std::endl;
}
}
}
}

int main(int argc, char *argv[])
{
try
{
// start the master function
return master(&failing_worker);
}
catch (const std::exception &ex)
{
std::cerr << ex.what() << std::endl;
}
catch (...)
{
std::cerr << "Unknown exception" << std::endl;
}
return EXIT_FAILURE;
}



- - - Aktualisiert - - -

@HaWe:

man 3 pthread_kill



NOTES
Signal dispositions are process-wide: if a signal handler is installed, the handler will be invoked in the thread thread, but if the disposition of
the signal is "stop", "continue", or "terminate", this action will affect the whole process.



Das bedeutet, das du nicht den thread töten kannst. Du rufst nur "kill" auf im Kontext des thread thread.

- - - Aktualisiert - - -

Das hier zeigt es ganz gut:

http://openbook.rheinwerk-verlag.de/linux_unix_programmierung/Kap10-011.htm

HaWe
17.06.2019, 19:33
@HaWe:

man 3 pthread_kill

Das bedeutet, das du nicht den thread töten kannst. Du rufst nur "kill" auf im Kontext des thread thread.



das verstehe ich nicht:
pthread_kill soll doch einen anderen thread killen - und du sagst, das tut es nicht?

schorsch_76
17.06.2019, 19:43
das verstehe ich nicht:
pthread_kill soll doch einen anderen thread killen - und du sagst, das tut es nicht?

Nein, das tut es nicht. Wenn du SIGKILL sendest, wird der ganze Prozess getötet. Das sagt die Dokumentaton zur API. man 3 pthread_kill. SIGKILL kann nicht geblockt werden.

HaWe
17.06.2019, 19:44
und wie geht es dann richtig, so wie beabsichtigt?

schorsch_76
17.06.2019, 19:47
https://en.wikipedia.org/wiki/Signal_(IPC)#SIGKILL



SIGKILL
The SIGKILL signal is sent to a process to cause it to terminate immediately (kill). In contrast to SIGTERM and SIGINT, this signal cannot be caught or ignored, and the receiving process cannot perform any clean-up upon receiving this signal. The following exceptions apply:

Zombie processes cannot be killed since they are already dead and waiting for their parent processes to reap them.
Processes that are in the blocked state will not die until they wake up again.
The init process is special: It does not get signals that it does not want to handle, and thus it can ignore SIGKILL.[6] An exception from this exception is while init is ptraced on Linux.[7][8]
An uninterruptibly sleeping process may not terminate (and free its resources) even when sent SIGKILL. This is one of the few cases in which a UNIX system may have to be rebooted to solve a temporary software problem.

SIGKILL is used as a last resort when terminating processes in most system shutdown procedures if it does not voluntarily exit in response to SIGTERM. To speed the computer shutdown procedure, Mac OS X 10.6, aka Snow Leopard, will send SIGKILL to applications that have marked themselves "clean" resulting in faster shutdown times with, presumably, no ill effects.[9] The command killall -9 has a similar, while dangerous effect, when executed e.g. in Linux; it doesn't let programs save unsaved data. It has other options, and with none, uses the safer SIGTERM signal.


- - - Aktualisiert - - -

Wie es richtig geht, zeigt mein Beispiel: Du kannst einen Thread nicht töten. Du kannst ihn nur so programmieren dass du ihn im jeden Fall sauber beenden kannst (join). Wenn das keine Option ist, geht nur der Subprozess den du mit SIGKILL immer töten kannst.

HaWe
17.06.2019, 19:50
deinen Code verstehe ich leider überhaupt nicht - was macht der in einfachen Worten?

PS
was meinst du mit "Subprozess" den ich "immer töten kann" ?

schorsch_76
17.06.2019, 19:53
Ich würde alle deine Threads als Subprozesse ausführen und mit ihnen mit Pipes, socketpair, posix mq oder was auch immer reden. Dann kannst du jeden deiner Prozesse abschießen und neu starten.

- - - Aktualisiert - - -

Er startet einen Subprozess per fork(), Sendet den Subprozess über eine pipe eine int Zahl. Der Subprozess ließt diese Zahl von der Pipe. Würfelt, bei 6 simuliert der Subprozess ein wait_forever(), bei 7 eine anormale Programmterminierung und in jedem anderen Fall schickt er es über die zweite Pipe zurück.

Das wiederholt sich endlos.

HaWe
17.06.2019, 20:03
ich verstehe folgende Dinge nicht:
Subprozess
worker
Pipes
socketpair
posix mq
fork
wait_forever()

daher denke ich, das ist 2 Nummern zu schwierig für mich.
Da ich deinen Code nicht verstehe, kann ich auch meinen Code nicht anpassen und umschreiben, leider...

Es soll nun mal ein Thread, der irgendwie geblockt ist oder sich aufgehängt hat warum auch immer (hängt quasi in 1 Programmzeile fest, ohne dass er seine loop- (while() )-Scheife weiter durchläuft), einzeln beendet werden, ohne die Stabilität meines Programms zu stören, und dann soll er neu gestartet werden.

schorsch_76
17.06.2019, 20:04
Hast du meine Code übersetzt bekommen?

HaWe
17.06.2019, 20:07
habe ich ehrlich gesagt noch gar nicht probiert, denn ich weiß noch gar nicht, was da passiert und ob ich es nutzen kann.
Der Code von dir müsste in mein Beispiel eingesetzt werden, für meine Arduino-Raspi-UART-Kommunikation damit es für mich Sinn macht... :rolleyes:

schorsch_76
17.06.2019, 20:08
Subprozess: Wenn du in der bash ein Program startest, ist das Program ein Subprozess deiner bash
worker, ein name für den Subprozess. Er soll einfach Arbeit verrichten

Alles IPC (Inter Prozess Kommunikation)
Pipes
socketpair
posix mq

fork(): Startet einen Subprozess. Siehe oben
wait_forever(): Das simuliert deinen hängenden Prozess der auf nichts anderes mehr reagiert.

HaWe
17.06.2019, 20:09
was ist eine bash und was ist ein bash worker...?? :D

schorsch_76
17.06.2019, 20:12
Mein Code ist voll mit Kommentaren. Die einzelnen Funktionen sind recht kurz. Versuch den Code zu übersetzen und beobachte ihn mit htop. Versuche die einzelnen Funktionen zu verstehen. Dann klappt das :)

HaWe
17.06.2019, 20:15
sorry, was ist htop?
und wie geht das dann später, wenn mein thread (der, der u.U. mal hängt) in 1-ms loops 1 kB-Strings an den Arduino sendet und sofort wieder 1kB-Strings abholt (> 50 GPIOs und Sensorwerte lesen/schreiben in Echtzeit), und diese Strings (und ihre daraus errechneten/extrahierten Zahlenwerte) sofort auch für alle meine anderen Threads in Echtzeit (1ms) zur Verfügung stehen sollen?

schorsch_76
17.06.2019, 20:23
htop: https://wiki.ubuntuusers.de/htop/
bash: Shell unter Linux. Wird gestartet wenn duc duch mit ssh oder vom GUI und dem Terminal mit deinem Pi verbindest.

Wenn der hängt, wird der Subprozess gekillt und ein neuer gestartet. Das macht mein Beispiel.
Die Werte die der Kollege per UART/tty/RS232 liefert werden einfach per Inter Prozess Kommunikation an die anderen Prozesse/threads weiter gegeben.

HaWe
17.06.2019, 20:28
ach so, momentan starte ich mein Programm direkt aus der Geany IDE heraus (F5) - oder aus dem Filemanager (leafpad).
ssh verwende ich nie, ich kompiliere lokal auf dem Pi (mit der Geany IDE).
Ich verstehe aber auch das Konzept mit dem Subprozess nicht: heißt das, mein Programm startet ein 2.Programm?

schorsch_76
17.06.2019, 20:32
ja: Aber das Programm das ausgeführt wird, ist nur eine andere Funktion in deinem Hauptprogramm. In meinem Beispiel failing_worker().

HaWe
17.06.2019, 20:39
ok, danke -
ich werde mich jetzt am besten mit deinem Code mal 2 Jahre in eine Einsiedelei zurückziehen (mit Internet) und versuchen darüber zu meditieren...
(ich hatte ehrlich gesagt gehofft, du könntest meinen obigen Code mit einer Hand voll Zusatzzeilen so verbessern, dass er dennoch meinen hängenden Thread einzeln killt und neu startet... ;) )

schorsch_76
17.06.2019, 20:48
34223

Du siehst hier den "master()" und im Baum weiter unten den "worker" der die Funktion faiing_worker() ausführt.

- - - Aktualisiert - - -

Empfehlung zur Linux Programmierung hab ich weiter oben schon geschrieben. Das Buch ist sehr gut!

HaWe
17.06.2019, 20:58
habe jetzt noch mal etwas genauer deinen Code studiert, aber nutzt du denn wirklich pthread? sieht irgendwie wie std::thread aus, das verwende ich aber ja gar nicht...

schorsch_76
17.06.2019, 21:05
Nein, ich nutze keinen Thread. Nur fork ()

- - - Aktualisiert - - -

Bzw sleep_for um zu schlafen. Das ist c++11

HaWe
17.06.2019, 21:06
ok, aber selbst wenn ich sähe, dass es funktioniert, dann wüsste ich dennoch nicht, wie ich das in meinen Code reinbringen soll.
Danke aber auf jeden Fall für die Mühe!

schorsch_76
19.06.2019, 06:11
@HaWe: Das hier ist der komplette Code der Laufen soll?

https://www.roboternetz.de/community/threads/73470-UART-Kommunikation-zwischen-Raspi-und-Arduino-hängt-sich-ständig-auf?p=652711&viewfull=1#post652711

Das bedeutet du hast nur das Protokoll zum AVR und einen Keyboard Thread und einen UART Thread. Richtig? Das ganze soll dann "irgendwie verheiratet werden" was aber noch nicht da ist.

Das schaut dann so aus:



Aktueller Stand

+---------+ +-------------+ +--------+
| AVR |+---->ttyACM0----->| UART Thread |+---->| Logik |
+---------+<-----------------++-------------+<----+| |
| |
| |
+---------+ +-------------+ | |
| Keybrd |+----------------->| Kbrd Thread |+---->| |
+---------+<-----------------++-------------+<----+| |
| |
| |
| |
| |
+--------+


Dazu soll dann noch vermutlich IO vom PI?

Hier kann man ASCII malen ;)
http://stable.ascii-flow.appspot.com

HaWe
19.06.2019, 07:42
@HaWe: Das hier ist der komplette Code der Laufen soll?


Dazu soll dann noch vermutlich IO vom PI?

Hier kann man ASCII malen ;)
http://stable.ascii-flow.appspot.com

hallo,
das ist erst die Basis, die tokens werden dann ausgewertet und als Sensorwerte verarbeitet oder die entsprechenden GPIOs geschaltet.
Dadurch stehen dann dem Raspi zusätzlich z.B. die 16 ADCs und die 54 digital GPIOS des Mega zur Verfügung plus die Peripherie, die am Mega angeschlossen ist.
Es kommen dann weitere threads hinzu, z.B. für Encder lesen, Navigation aus Odometrie und IMU+GPS, Fernsteuerung, Bildschirmanzeige, PID-Regler, autonome Aktionen wie Wegefindung u/o SLAM.

PS,
ich bin an einer Sache dran, kritische threads asynchron zu starten und mit pthread_cancel ggf. zu stoppen...

schorsch_76
19.06.2019, 08:21
Da du pthread_cancel ansprichts, hier gibt es etwas zu lesen für dich:
https://lwn.net/Articles/683118/

Das Problem das ich hier eben sehe, das mein Design mit den Prozessen nicht hat, sind nicht frei gegebene Betriebsystemresourcen wie bsp der offene Filedescriptor für ttyACM0. Da dein Projekt ja der RB6 ist und er recht lange laufen soll, könnte es passieren dass deinem Prozess die Filedescriptoren aus gehen, wenn diese nicht frei gegeben werden. Den Wert kann man erhöhen. Standardmäßig ist er glaub 1024, aber er kann nicht beliebig hoch gesetzt werden wegen begrenzter Resourcen auf dem Zielsystem.

Zur Erklärung ein Beispiel:
Wenn der ttyACM0 vom USB abgezogen wird, während der Thread hier einen Handle hat (auch wiringPi setzt auf dem Kernel auf), ist der FD immer noch offen. Du machst pthread_cancel und restart und du hast 2 offene FD's. Den alten und den neuen FD. Zumindest in der Filedescriptor Tabelle des Prozesses. Das kannst du sehen wenn du ein "ls /proc/${deine PID}/fd" ansiehst.

Da das Projekt auf dem Pi noch recht am Anfang steht, würde ich das versuchen zu beachten. Wenn du sagst, das ist für dich kein Thema, dann ok :)

HaWe
19.06.2019, 08:42
hallo,
danke für die Infos!
den int fd bekomme ich über wiringSerial: ich öffne ja UART per fd=serialOpen("/dev/ttyACM0", clock);
und wenn ich den Thread abbrechen musste, hänge ich sicherheitshalber noch ein serialClose(fd) hinten dran - dann müsste ja der fd wieder gelöscht/freigegeben sein, oder?

(erst danach öffne ich dann per fd=serialOpen("/dev/ttyACM0", clock) erneut.)


edited

schorsch_76
19.06.2019, 08:45
Das könnte klappen... :)

Du musst dich aber um alle Resourcen kümmern. Am besten alles im Heap halten dann bei Close(fd) auch freigeben. std::string und co jegen große Sachen aber auch im Heap ab. Damit wird der Speicher voll und voller...

Edit: Heap: Frei Speicher.



std::string* buffer = new std::string;


beim Freigeben


Close(fd);
delete buffer;
...

HaWe
19.06.2019, 08:57
abere signore, isch 'abe doche gare keine std::threade :p

schorsch_76
19.06.2019, 09:02
Das ist egal, auch dein pthread den du mit pthread_cancel abbrichst, muss die Resourcen frei geben.

HaWe
19.06.2019, 09:06
theoretisch klar, aber ich weiß nicht wie, andererseits: so viel reservierten Speicher gibt es ja gar nicht.

Ich kann auch einige Variablen sicherheitshalber global deklarieren.

Klebwax
19.06.2019, 10:00
Da du pthread_cancel ansprichts, hier gibt es etwas zu lesen für dich:
https://lwn.net/Articles/683118/

Das Problem das ich hier eben sehe, das mein Design mit den Prozessen nicht hat, sind nicht frei gegebene Betriebsystemresourcen wie bsp der offene Filedescriptor für ttyACM0.

Das ist ein grundsätzliches Problem. Am Anfang glaubt man, man kann sich die Interprozesskommunikation sparen, wenn man Threads statt Tasks benutzt. Man kann einfach globale Variable verwenden. Dazu kommt noch die verbreitete Meinung, Taskwechsel verschenken Zeit gegenüber Threadwechseln. Irgendwann, wenn das System unübersichtlich geworden ist und sich z.B. die ersten Memoryleaks oder Deadlocks eingeschlichen haben, fängt man an, eine Thread- und Speicherüberwachung zu programmieren. Zum Schluß stellt man dann fest, daß man ein eigenes Betriebssystem programmiert hat, das aber längst auf dem System vorhanden ist. Das eigene ist auch nicht einfacher als das vorhandene, dafür ist es nur von einem selbst getestet und es fehlen hunderte Seiten man-Pages. Wer schreibt schon sowas zum eigenen Code? Wegen der Threadüberwachung ist es sogar langsamer als ein System, das auf Tasks basiert.

Daher sollte man am Anfang des Systemdesigns gut überlegen, ob es Sinn macht, ein eigenes Tasking-System (genauso wie eigene Kommunikationsprotokolle oder eine eigene Datenserialiesung) zu erfinden. Am Ende lauert immer das komplette Fiasko.

MfG Klebwax

HaWe
19.06.2019, 11:22
Das ist ein grundsätzliches Problem. Am Anfang glaubt man, man kann sich die Interprozesskommunikation sparen, wenn man Threads statt Tasks benutzt. Man kann einfach globale Variable verwenden. Dazu kommt noch die verbreitete Meinung, Taskwechsel verschenken Zeit gegenüber Threadwechseln. Irgendwann, wenn das System unübersichtlich geworden ist und sich z.B. die ersten Memoryleaks oder Deadlocks eingeschlichen haben, fängt man an, eine Thread- und Speicherüberwachung zu programmieren. Zum Schluß stellt man dann fest, daß man ein eigenes Betriebssystem programmiert hat, das aber längst auf dem System vorhanden ist. Das eigene ist auch nicht einfacher als das vorhandene, dafür ist es nur von einem selbst getestet und es fehlen hunderte Seiten man-Pages. Wer schreibt schon sowas zum eigenen Code? Wegen der Threadüberwachung ist es sogar langsamer als ein System, das auf Tasks basiert.

Daher sollte man am Anfang des Systemdesigns gut überlegen, ob es Sinn macht, ein eigenes Tasking-System (genauso wie eigene Kommunikationsprotokolle oder eine eigene Datenserialiesung) zu erfinden. Am Ende lauert immer das komplette Fiasko.

MfG Klebwax

dein Einwand trifft hier nicht zu, denn wegen (höchstwahrscheinlich) kernel Zugriffen funktioniert hier keine UaRT Kommunikation über Stunden hinweg stabil in einer main loop (es hatte sich ja immer aufgehängt nach mehreren Minuten, wenn du dich recht erinnerst) , und wenn mal eine main loop hängen sollte, ist dann komplett Hopfen und Malz verloren.
Außerdem ist aber ja doch das gesamte Prgrommdesign eh auf MT ausgelegt (SCHED_RR mit verschiedenen prios).
Die Threadüberwachug aber läuft zwar in einem high prio Thread, aber später mit langen yields/delays von mindestens 100ms pro loop (oder noch viel länger), die auf einem quadcore überhaupt nicht ins Gewicht fallen, zumal die yield Zeiten in vollem Umfang vom Scheduler den anderen time slices zur Verfügung gestellt werden.

- - - Aktualisiert - - -

PS
oder habe ich deinen Bezug falsch intepretiert?

Klebwax
19.06.2019, 11:28
dein Einwand trifft hier nicht zu, denn wegen (höchstwahrscheinlich) kernel Zugriffen funktioniert hier keine UaRT Kommunikation über Stunden hinweg stabil in einer main loop (es hatte sich ja immer aufgehängt nach mehreren Minuten, wenn du dich recht erinnerst) , und wenn mal eine main loop hängen sollte, ist dann komplett Hopfen und Malz verloren.
Außerdem ist aber ja doch das gesamte Prgrommdesign eh auf MT ausgelegt (SCHED_RR mit verschiedenen prios).
Die Threadüberwachug aber läuft zwar in einem high prio Thread, aber später mit langen yields/delays von mindestens 100ms pro loop (oder noch viel länger), die auf einem quadcore überhaupt nicht ins Gewicht fallen, zumal die yield Zeiten in vollem Umfang vom Scheduler den anderen time slices zur Verfügung gestellt werden.

Du hast meinen Text nicht wirklich verstanden. Daher hier noch mal die wichtigsten Worte


Threads statt Tasks

MfG Klebwax

Da du deinen Text ergänzt hast hier meine Ergänzung: Ja

HaWe
19.06.2019, 11:36
meintest du mit Tasks die Prozesse und Sub-Prozesse mit dem pipe Ding von schorsch?

schorsch_76
19.06.2019, 19:10
@HaWe: Genau: Er meint man sollte die Aufgaben in einzelne Prozesse verlagern. Auch die Prozesse werden vom Scheduler des Betriebsystems richtig priorisiert.

Klebwax
19.06.2019, 20:35
@HaWe: Genau: Er meint man sollte die Aufgaben in einzelne Prozesse verlagern.
So einfach hab ich das nicht gesagt. Ich hab gesagt, daß man sich die Entscheidung ob Task oder Thread (oder auch beides) nicht zu leicht machen sollte. Ein Designfehler an dieser Stelle kann zum völligen Fehlschlag des Projektes führen.


Auch die Prozesse werden vom Scheduler des Betriebsystems richtig priorisiert.

"Auch" halte ich hier für eine unglückliche Formulierung. Unix, und das gilt auch für seine seine Clones, ist ein Multitasking System mit gekapselten Prozessen, die in eigenen Adressräumen laufen. Erst später sind Threads dazu gekommen. Der Scheduler kümmert sich also primär um Tasks und erst in zweiter Linie um die Threads der Tasks. Er arbeitet auch, wenn, wie früher. kein einziger Thread aufgesetzt wird.

MfG Klebwax

HaWe
19.06.2019, 21:13
ist es nicht so, dass seit den POSIX C Definitionen insb. zu pthread (irgendwann schon in den 90ern) das MT per pthread eingeführt wurde?
Ich arbeite ja mit pthread, und seit C99 spätestens ist das ja Standard.
Übrigens, ich habe jetzt erfolgreich eine Methode getestet, die ich von einem netten Forumsmember des Raspi Forums bekommen habe (Paeryn).
Ist zwar auch nicht gerade leichte Kost, aber fügt sich prima in meine pthread MT Architektur ein! :)
Und: Es klappt jetzt damit mit dem Abbrechen eines einzelnen Threads! 8)

Jetzt will ich sehen, ob ich ihn auch wieder starten kann und dann ob das auch auf die UART Kommunikation ausgeweitet werden kann... 8)



// Quelle. https://www.raspberrypi.org/forums/posting.php?mode=quote&f=33&p=1481807
#include <stdio.h>
#include <pthread.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdlib.h>

volatile bool TASKS_ACTIVE = false;
volatile bool HEARTBEAT = false;

// This is the cleanup function for UART_thr, it will be called if the
// thread is cancelled or exits via pthread_exit(). Use it to release any
// resources the thread has.
void UART_thr_cleanup(void* data)
{
printf("UART_thr cleanup.\n");
}

void* UART_thr(void* data) // UART comm (now just stalling)
{
int old;
if (pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS , &old)) {
printf("UART: Error setting async cancel state.\n");
pthread_exit(NULL);
}
pthread_cleanup_push(UART_thr_cleanup, NULL);
HEARTBEAT = true;
for(int i = 0; i < 5; i++) {
printf("UART: i = %d\n", i);
sleep(1);
}
printf("UART: simulating stall\n");
HEARTBEAT = false;
while(1) ; // mimics stalling forever
pthread_cleanup_pop(true);
return NULL;
}


// We pass in a pointer to the thread id which we may need to cancel
void* WATCHER_thr(void* heartbeat_id) // thread watcher
{
pthread_t heartbeat_thread_id = *(pthread_t*)heartbeat_id;
bool THREAD_ACTIVE = true;
printf("WATCHER: Checking heartbeat.\n");
while(THREAD_ACTIVE) { //
// cancel UART thread if no heart beat detected
if(!HEARTBEAT) {
// cancel UART thread // <~~~~~~~~~ ???? pthread_kill ? pthread_cancel ?
printf("WATCHER: Lost heartbeat, cancelling UART thread.\n");
pthread_cancel(heartbeat_thread_id);
THREAD_ACTIVE = false;
}
}
TASKS_ACTIVE = false;
return NULL; //
}


//

int main() {

// threads
pthread_t thread0, thread1, thread2;
pthread_attr_t thread_attr;
struct sched_param param;

// start multithreading
pthread_attr_init(&thread_attr); // Initialise the attributes
pthread_attr_setschedpolicy(&thread_attr, SCHED_RR); // Set attributes to RR policy

param.sched_priority = 40;
pthread_attr_setschedparam(&thread_attr, &param); // Set attributes to priority 40 (policy is already RR)
pthread_create(&thread2, &thread_attr, UART_thr, NULL); // medium priority: UART

param.sched_priority = 80;
pthread_attr_setschedparam(&thread_attr, &param); // Set attributes to priority 80
pthread_create(&thread0, &thread_attr, WATCHER_thr, &thread2); // high priority: heartbeat monitor

pthread_attr_destroy(&thread_attr); // We've done with the attributes

TASKS_ACTIVE = true;
while(TASKS_ACTIVE) {
printf("MAIN: tasks active, waiting\n");
sleep(1);
}
printf("MAIN: threads ended, goodbye.\n");

// wait for threads to join before exiting
pthread_join(thread0, NULL);
//pthread_join(thread1, NULL);
pthread_join(thread2, NULL);

exit(0);
}

Klebwax
20.06.2019, 07:27
ist es nicht so, dass seit den POSIX C Definitionen insb. zu pthread (irgendwann schon in den 90ern) das MT per pthread eingeführt wurde?

Nein.
Das wäre ja ein Schritt zurück zu Windows 3.11 mit darunter liegendem DOS ohne Speicherschutz. Ich zitiere mal hier (https://www.geeksforgeeks.org/multithreading-c-2/) (Prozess und Task sind Synonyme)


What is a Thread?
A thread is a single sequence stream within in a process. Because threads have some of the properties of processes, they are sometimes called lightweight processes.

What are the differences between process and thread?
Threads are not independent of one other like processes as a result threads shares with other threads their code section, data section and OS resources like open files and signals. But, like process, a thread has its own program counter (PC), a register set, and a stack space.
Ein Thread kann also den anderen Threads den Code überschreiben, in den Speicher schreiben oder auch einen File schließen. Das macht einem im positiven Fall das Leben leichter (keine Interprozesskommunikation nötig) bringt aber im negativen Fall alle Threads gemeinsam zum Absturz.

MfG Klebwax

HaWe
20.06.2019, 08:22
wieso schreibst du jetzt was zu Windows? Windows ist a-priori nicht POSIX-kompatibel.
Tatsächlich (habe nachgeguckt) is threads eine Standard POSIX Extension seit 1995:

Threads is an API defined by the standard POSIX.1c, Threads extensions (IEEE Std 1003.1c-1995). https://en.wikipedia.org/wiki/POSIX_Threads

Aber lassen wir das mit der Historie und den Grundsatzdiskussionen:
pthread ist ANSI C99 Standard, pthread ist relativ einfach anzuwenden (sogar einfacher als std::thread finde ich), und es ist seit langem erprobt, bewährt und ohne Bugs.

Ich bn ja selber kein ausgebildeter Programmierer sondern Laie und Hobby-Anwender, und als Laie kann ich sagen, dass man sich zwar schon einarbeiten und etliche Fragen klären muss, aber wenn man das tut, dann ist es auch für Laien anwendbar (das ist mit den ganzen Fuses und Interrupts in avrstudio sicher nicht anders).
Immerhin ist pthread zur Implementierung deutlich weniger komplex als Subprozesse, daher würde ich pthread in jedem Falle den Vorzug geben.

oberallgeier
20.06.2019, 08:25
.. Ich zitiere mal hier (https://www.geeksforgeeks.org/multithreading-c-2/) (Prozess und Task sind Synonyme) ..Danke für so ne Quellenangabe. Für mich sind "joinable", "detached", Thread, Prozess, Task etc im Kontext zu Programmen bzw. Programmierung eher Fremdwörter in denen ich nur sehr bescheidene Inhalte sehe, von irgendeinem Wissen zu Systemdesign mal ganz abgesehen. Das ist sehr persönlich - leider. Aber genau deswegen lese ich so etwas intensiv mit in der Hoffnung dass mal ein Schimmer aufleuchtet. Dein Link hilft da (ungemein?), danke. Und jetzt schweige ich hier wieder und lese nur noch.

HaWe
20.06.2019, 08:47
PS, back to topic:
ein wenig klarer ist es mir ja jetzt geworden, was "pthread_join" macht, nach meinem begrenzten Verständnis, in einfachen Worten (nicht Expertenmode-sicher ;) ):
- es wird im eigentlichen Wortsinn nichts von den ursprünglichen Thread-Funktionen (Thread-Code) gejoint, d.h., der Thread selber "tritt main() nicht bei" bzw. "hängt sich main() nicht an".
- es wird auch kein Thread sicher beendet, sondern nur darauf gewartet, dass der Thread endet
- dennoch müssen hnterher noch Speicher und Pointer etc. aufgeräumt werden, z.B. existiert immer noch ein thread-Handle über die thread-ID, und auch die wird erst durch pthread_join gelöscht.
- auch wenn der Thread sich nicht selber beendet, sondern er über pthread_cancel beendet wurde, ist der Thread handle noch nicht geschlossen, auch das muss noch durch ein zusätzliches pthread_join erledigt werden.

Klebwax
20.06.2019, 09:19
wieso schreibst du jetzt was zu Windows? Weil das "Multitasking" von Windows 3.11 nur Threads kannte und keine Tasks. Unix benutzt schon immer Tasks und wird sicher nie sein "Multitasking" auf so eine unsichere Umgebung wie Threads umstellen, POSIX hin oder her.


Aber lassen wir das mit der Historie und den Grundsatzdiskussionen:
pthread ist ANSI C99 Standard, pthread ist relativ einfach anzuwenden (sogar einfacher als std::thread finde ich), und es ist seit langem erprobt, bewährt und ohne Bugs.

Es geht nicht um Bugs im System, es geht um die, die Du programmierst. Führt ein Bug in einem deiner Threads zum Absturz oder überschreibt einer deiner Threads die Variablen eines anderen Threads, stürzt nicht nur der eine Thread sondern auch alle anderen ab. Dies ist der Nachteil, den man sich einhandelt, wenn man sich die Komplexität im Umgang mit Tasks sparen will. Und dies ist auch der Grund, warum Windows seit NT auch auf Tasks basiert (und damit erst wirklich brauchbar geworden ist).


Immerhin ist pthread zur Implementierung deutlich weniger komplex als Subprozesse, daher würde ich pthread in jedem Falle den Vorzug geben.

"There is no such thing as a free lunch"

MfG Klebwax

P.S.

- dennoch müssen hnterher noch Speicher und Pointer etc. aufgeräumt werden, z.B. existiert immer noch ein thread-Handle über die thread-ID, und auch die wird erst durch pthread_join gelöscht.
- auch wenn der Thread sich nicht selber beendet, sondern er über pthread_cancel beendet wurde, ist der Thread handle noch nicht geschlossen, auch das muss noch durch ein zusätzliches pthread_join erledigt werden.

Das alles und noch viel, auf das du noch kommen wirst, erledigt bei einer Task das Betriebssystem.


Zum Schluß stellt man dann fest, daß man ein eigenes Betriebssystem programmiert hat, das aber längst auf dem System vorhanden ist. Das eigene ist auch nicht einfacher als das vorhandene, dafür ist es nur von einem selbst getestet und es fehlen hunderte Seiten man-Pages. Wer schreibt schon sowas zum eigenen Code? Wegen der Threadüberwachung ist es sogar langsamer als ein System, das auf Tasks basiert.

HaWe
20.06.2019, 09:28
Deine Grundsatzdiskussionen helfen nicht wirklich weiter. In C aber ist immer schon vieles möglich, was zu Überschreiben von Speicherbereichen führt: In C muss nunmal der Programmierer aufpassen, was er tut, es ist von der Sprache her (fast) alles erlaubt, auch damit verbundene Fehler.
Ich sehe auch nicht, dass du hier eine ernsthafte, praktikable Alternative zu pthread anbieten kannst, die u.a. auch halbwegs Hobbyprogrammierer-tauglich ist (Prozesse und Subprozesse mit pipes etc sind es definitv NICHT!).
Also lassen wir jetzt bitte diese frustranen und sinnlosen Grundsatzdiskussionen ;)

Klebwax
20.06.2019, 09:49
Deine Grundsatzdiskussionen helfen nicht wirklich weiter. In C aber ist immer schon vieles möglich, was zu Überschreiben von Speicherbereichen führt:
Von einer Task in eine andere??



In C muss nunmal der Programmierer aufpassen, was er tut, es ist von der Sprache her (fast) alles erlaubt, auch damit verbundene Fehler.
Und das Multitasking sorgt dafür, daß es nicht jedesmal einen Reboot des Gesamtsystems gibt.

Ich will auch nichts anbieten, ich will dich nur dazu bringen, die Konsequenzen deiner Entscheidung für Threads zu realisieren. "einfacher" wäre mir als Begründung zu wenig.

Aber du hast recht, es ist genug gesagt, du hast nur noch nicht alles verstanden

MfG Klebwax

HaWe
20.06.2019, 09:57
nein, ich denke du hast noch nicht alles verstanden, nämlich den Gesamtkontext und Sinn und Zweck der Anwendung, und POSIX pthread ist immerhin seit 20 Jahren eine etablierte ANSI C Standardlib.
Irgendwas schlechtreden ohne konstruktive Alternativen ist aber in jedem Falle eine ziemlich billige Attitüde.