Threading *und* zweimal Forken (Daemon)

Bitte habt Geduld mit mir, ich bin Python-Newbie, und kenn mich auch sonst programmiertechnisch nicht so gut aus, das vorweg.
Ich möchte in einem Python-Skript (Python, weil es da ein paar gute Libraries für das was ich machen möchte gibt, nicht weil ich mich da gut auskenne) einen Hintergrundprozeß realisieren, der beim Systemstart gestartet wird, und der einige Sensoren über eine serielle Schnittstelle ausliest, ein paar Zeichen an ein seriell angehängtes Display ausgibt, und auch sonst noch einiges macht (z.B. Logging über rrdtool).
Als Vordergrundskript habe ich das aus diversen Funstücken und Libraries schon brauchbar zusammengestöpselt, wobei ich anhand eines gefundenen Beispiels das Auslesen der seriellen Schnittstelle mit Threading realisiert habe: Ein Thread liest die serielle Schnittstelle aus, und speichert den letzten Datensatz in einer globalen Variable, die im Haupt-Thread ausgelesen und z.B. per RRDtool geloggt wird.
Das entscheidende Schnipsel (das ich aus einem Beispiel übernommen habe), das dieses Auslesen übernimmt, das sieht so aus:
from threading import Thread
[...]
def receiving(ser): global last_received buffer='' while True: buffer = buffer + ser.read(256) if '\n' in buffer: lines = buffer.split('\n') last_received = lines[-2] buffer = lines[-1]
Thread(target=receiving, args=(ser,)).start()
nach: http://stackoverflow.com/questions/1093598/pyserial-how-to-read-the-last-lin...
Das funktioniert bis jetzt scheinbar zuverlässig als Skript im Vordergrund.
Würde aus eurer Sicht etwas dagegen zu sprechen, das nach diesem Kochrezept von Sander Marechal in einen Daemon zu verwandeln:
http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/
Würde ich da einfach mein ganzes Hauptprogramm des Skripts, und die Zeile die den Thread startet in die Funktion "run" stopfen? Geht das prinzipiell? Oder ist das ein Problem, wenn man das zweimalige Forken mit Multithreading kombiniert?
TIA
/ralph -- ja, ich will das besser verstehen, aber einstweilen will ich mal einen laufenden Prototypen haben, den ich nicht dauernd mit "nohup" starten muß.

On 12/5/12 4:35 PM, "Ralph Aichinger" ralph@pangea.at wrote:
Bitte habt Geduld mit mir, ich bin Python-Newbie, und kenn mich auch sonst programmiertechnisch nicht so gut aus, das vorweg.
Ich möchte in einem Python-Skript (Python, weil es da ein paar gute Libraries für das was ich machen möchte gibt, nicht weil ich mich da gut auskenne) einen Hintergrundprozeß realisieren, der beim Systemstart gestartet wird, und der einige Sensoren über eine serielle Schnittstelle ausliest, ein paar Zeichen an ein seriell angehängtes Display ausgibt, und auch sonst noch einiges macht (z.B. Logging über rrdtool).
Als Vordergrundskript habe ich das aus diversen Funstücken und Libraries schon brauchbar zusammengestöpselt, wobei ich anhand eines gefundenen Beispiels das Auslesen der seriellen Schnittstelle mit Threading realisiert habe: Ein Thread liest die serielle Schnittstelle aus, und speichert den letzten Datensatz in einer globalen Variable, die im Haupt-Thread ausgelesen und z.B. per RRDtool geloggt wird.
Das entscheidende Schnipsel (das ich aus einem Beispiel übernommen habe), das dieses Auslesen übernimmt, das sieht so aus:
from threading import Thread
[...]
def receiving(ser): global last_received buffer='' while True: buffer = buffer + ser.read(256) if '\n' in buffer: lines = buffer.split('\n') last_received = lines[-2] buffer = lines[-1]
Thread(target=receiving, args=(ser,)).start()
nach: http://stackoverflow.com/questions/1093598/pyserial-how-to-read-the-last-l ine-sent-from-a-serial-device
Das funktioniert bis jetzt scheinbar zuverlässig als Skript im Vordergrund.
Würde aus eurer Sicht etwas dagegen zu sprechen, das nach diesem Kochrezept von Sander Marechal in einen Daemon zu verwandeln:
http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python /
Würde ich da einfach mein ganzes Hauptprogramm des Skripts, und die Zeile die den Thread startet in die Funktion "run" stopfen? Geht das prinzipiell? Oder ist das ein Problem, wenn man das zweimalige Forken mit Multithreading kombiniert?
Kurz: ja und nein. Alles in ein Skript spricht nix gegen, du solltest - gerade bei forks - nichts auf module-Ebene machen, sondern klare Funktionen haben.
Denn sonst kommt die eine Hälfte vorm fork, die andere danach und so
Und nein, threading und forking kommen sich nicht in die Quere.
Diez

Am 05.12.2012 18:08, schrieb Diez Roggisch:
Kurz: ja und nein. Alles in ein Skript spricht nix gegen, du solltest - gerade bei forks - nichts auf module-Ebene machen, sondern klare Funktionen haben.
Denn sonst kommt die eine Hälfte vorm fork, die andere danach und soŠ
Und nein, threading und forking kommen sich nicht in die Quere.
Leider ist das so nicht richtig. fork und threads sollte man nur mit vorsicht mischen, weil man leicht dead locks erzeugen kann. Außerdem läuft im Child nur noch der Prozess, der fork() aufgerufen hat.
Ich empfehle folgendes Vorgehen:
* erst alle benötigten Module importieren * dann double fork zum Dämonisieren * erst zum Schluss die Geräte öffnen und Threads starten
Christian

On 12/5/12 6:23 PM, "Christian Heimes" christian@python.org wrote:
Am 05.12.2012 18:08, schrieb Diez Roggisch:
Kurz: ja und nein. Alles in ein Skript spricht nix gegen, du solltest - gerade bei forks - nichts auf module-Ebene machen, sondern klare Funktionen haben.
Denn sonst kommt die eine Hälfte vorm fork, die andere danach und soŠ
Und nein, threading und forking kommen sich nicht in die Quere.
Leider ist das so nicht richtig. fork und threads sollte man nur mit vorsicht mischen, weil man leicht dead locks erzeugen kann.
Kannst du das näher ausführen, warum die Kombination da Größere Probleme hat, als wahlweise Prozesse oder Threads?
Deadlocks entstehen doch um geteilte Ressourcen, die mittels geeigneter Mechanismen synchronisiert werden.
Warum sollte da ein Unterschied bestehen, ob Thread A und Thread B in Prozess A sich den Hahn abdrehen, vs. Thread A in Prozess A und Thread B in Prozess B?
Außerdem läuft im Child nur noch der Prozess, der fork() aufgerufen hat.
Den Satz verstehe ich nicht.
Ich empfehle folgendes Vorgehen:
- erst alle benötigten Module importieren
- dann double fork zum Dämonisieren
- erst zum Schluss die Geräte öffnen und Threads starten
Da stimme ich dir voll zu :)
Diez

Am 05.12.2012 18:41, schrieb Diez Roggisch:
Außerdem läuft im Child nur noch der Prozess, der fork() aufgerufen hat.
Den Satz verstehe ich nicht.
Ja, das wundert mich nicht. ;) Da sollte "Thread" und nicht "Prozess" stehen. Irgendwas ist auf dem Weg vom Gehirn zur Hand schief gelaufen.
http://linux.die.net/man/2/fork
The child process is created with a single thread—the one that called fork(). The entire virtual address space of the parent is replicated in the child, including the states of mutexes, condition variables, and other pthreads objects; the use of pthread_atfork(3) may be helpful for dealing with problems that this can cause.
Jetzt sollte dir auch klar sein, wie es zu dead locks kommen kann.
Christian

On 12/5/12 6:53 PM, "Christian Heimes" christian@python.org wrote:
Am 05.12.2012 18:41, schrieb Diez Roggisch:
Außerdem läuft im Child nur noch der Prozess, der fork() aufgerufen hat.
Den Satz verstehe ich nicht.
Ja, das wundert mich nicht. ;) Da sollte "Thread" und nicht "Prozess" stehen. Irgendwas ist auf dem Weg vom Gehirn zur Hand schief gelaufen.
http://linux.die.net/man/2/fork
The child process is created with a single threadthe one that called fork(). The entire virtual address space of the parent is replicated in the child, including the states of mutexes, condition variables, and other pthreads objects; the use of pthread_atfork(3) may be helpful for dealing with problems that this can cause.
Jetzt sollte dir auch klar sein, wie es zu dead locks kommen kann.
Ah, ok. Ich bin an sich eh davon ausgegangen, dass der OP die forks ja eh nur zur Dämonisierung vornehmen wollte - an sich ist das ein single-process-Szenario, und die Threads kommen nach dem letzten fork.
Diez

Hi,
On 05 Dec 2012 15:35:53 GMT Ralph Aichinger ralph@pangea.at wrote:
Ich möchte in einem Python-Skript (Python, weil es da ein paar gute Libraries für das was ich machen möchte gibt, nicht weil ich mich da gut auskenne) einen Hintergrundprozeß realisieren, der beim Systemstart gestartet wird, und der einige Sensoren über eine serielle Schnittstelle ausliest, ein paar Zeichen an ein seriell angehängtes Display ausgibt, und auch sonst noch einiges macht (z.B. Logging über rrdtool).
Als Vordergrundskript habe ich das aus diversen Funstücken und Libraries schon brauchbar zusammengestöpselt, wobei ich anhand eines gefundenen Beispiels das Auslesen der seriellen Schnittstelle mit Threading realisiert habe: Ein Thread liest die serielle Schnittstelle aus, und speichert den letzten Datensatz in einer globalen Variable, die im Haupt-Thread ausgelesen und z.B. per RRDtool geloggt wird.
Uh, das würde ich ändern. Globale Variablen sind nicht toll. Und eine einzelne Variable für den Austausch von möglicherweise mehreren Nachrichten zu nutzen ist doch recht fehleranfällig. Natürlich läuft Dein Skript jetzt auf nem i5 oder so und der Prozessor hat viel Zeit alle Aufgaben zu erledigen bevor das andere Gerät an der seriellen Schnittstelle überhaupt merkt, das die Daten gesendet sind. Aber wenn Du etwa die Serielle durch einen firewire oder netzwerk-draht ersetzt oder Deine Software auf andere Prozessoren wie den Rasperry oder noch Prozessoren mit python aber unterhalb der ARM-Klasse portierst, sehen die Verhältnisse schon wieder anders aus. Schon kann die nächste Nachricht kommen, während die erste noch nicht verarbeitet ist. Besser ist es da eine Queue zu verwenden, wo viele Zeilen/Messages drinne sein können. Und wenn ein thread/prozess nur liest und der andere nur schreibt, braucht es da nicht mal locking. Also schau Dir mal in multiprocessing die Queues an, die kann man auch mit threads verwenden.
Arnold

Hallo Arnold,
On 2012-12-05 22:33, Arnold Krille wrote:
On 05 Dec 2012 15:35:53 GMT Ralph Aichinger ralph@pangea.at wrote:
Als Vordergrundskript habe ich das aus diversen Funstücken und Libraries schon brauchbar zusammengestöpselt, wobei ich anhand eines gefundenen Beispiels das Auslesen der seriellen Schnittstelle mit Threading realisiert habe: Ein Thread liest die serielle Schnittstelle aus, und speichert den letzten Datensatz in einer globalen Variable, die im Haupt-Thread ausgelesen und z.B. per RRDtool geloggt wird.
Uh, das würde ich ändern. Globale Variablen sind nicht toll. Und eine einzelne Variable für den Austausch von möglicherweise mehreren Nachrichten zu nutzen ist doch recht fehleranfällig. [...]
das hatte ich zuerst auch gedacht. Bei dem verwendeten Algorithmus zum Update der Variable _sollte_ dieser Vorgang in CPython aber atomar sein, und soweit ich verstanden habe, greifen andere Threads auch nur lesend auf die Variable zu. So richtig sauber finde ich es aber auch nicht, sich auf die Atomizität von Operationen in bestimmten Python- Implementierungen zu verlassen.
ist. Besser ist es da eine Queue zu verwenden, wo viele Zeilen/Messages drinne sein können. Und wenn ein thread/prozess nur liest und der andere nur schreibt, braucht es da nicht mal locking.
Der "einfache" Queue-Ansatz hätte zur Folge, dass die Queue immer geleert werden muss, um an den aktuellsten Wert zu kommen. Wenn andere Threads die Queue "langsamer" auslesen als sie gefüllt wird, "sehen" sie immer veraltete Werte. Im konkreten Beispiel könnte der einfache Ansatz aber reichen, wenn der Hauptthread die Queue ständig ausliest.
Mir ist, unabhängig von der Art der Datenverteilung, noch ein anderer Aspekt aufgefallen: _Falls_ `ser.read` bei "Datenmangel" nicht blockiert, hat man "Busy Waiting", was wahrscheinlich nicht gerade effizient (stromsparend) ist.
Also schau Dir mal in multiprocessing die Queues an, die kann man auch mit threads verwenden.
Ich denke, hier geht es um die `Queue.Queue`-Klasse, nicht die in `multithreading`. :-) Die Verwendung ist aber gleich.
Viele Grüße Stefan
participants (5)
-
Arnold Krille
-
Christian Heimes
-
Diez Roggisch
-
Ralph Aichinger
-
Stefan Schwarzer