26 Stimmen

Unterbrechbare Fadenverbindung in Python

Gibt es eine Möglichkeit, auf die Beendigung eines Threads zu warten, aber trotzdem Signale abzufangen?

Bedenken Sie Folgendes C Programm:

#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>

void* server_thread(void* dummy) {
    sleep(10);
    printf("Served\n");
    return NULL;
}

void* kill_thread(void* dummy) {
    sleep(1); // Let the main thread join
    printf("Killing\n");
    kill(getpid(), SIGUSR1);
    return NULL;
}

void handler(int signum) {
    printf("Handling %d\n", signum);
    exit(42);
}

int main() {
    pthread_t servth;
    pthread_t killth;

    signal(SIGUSR1, handler);

    pthread_create(&servth, NULL, server_thread, NULL);
    pthread_create(&killth, NULL, kill_thread, NULL);

    pthread_join(servth, NULL);

    printf("Main thread finished\n");
    return 0;
}

Sie endet nach einer Sekunde und wird ausgedruckt:

Killing
Handling 10

Im Gegensatz dazu ist hier mein Versuch, es einzuschreiben Python :

#!/usr/bin/env python
import signal, time, threading, os, sys

def handler(signum, frame):
    print("Handling " + str(signum) + ", frame:" + str(frame))
    exit(42)
signal.signal(signal.SIGUSR1, handler)

def server_thread():
    time.sleep(10)
    print("Served")
servth = threading.Thread(target=server_thread)
servth.start()

def kill_thread():
    time.sleep(1) # Let the main thread join
    print("Killing")
    os.kill(os.getpid(), signal.SIGUSR1)
killth = threading.Thread(target=kill_thread)
killth.start()

servth.join()

print("Main thread finished")

Er druckt:

Killing
Served
Handling 10, frame:<frame object at 0x12649c0>

Wie kann ich erreichen, dass sie sich wie die C-Version verhält?

15voto

Jarret Hardie Punkte 89900

Threads in Python sind angesichts der globalen Interpretersperre etwas seltsame Biester. Sie sind möglicherweise nicht in der Lage, das zu erreichen, was Sie wollen, ohne auf ein Join-Timeout und isAlive zurückzugreifen, wie eliben vorschlägt.

Es gibt zwei Stellen in den Dokumenten, die den Grund dafür angeben (und möglicherweise weitere).

Die erste:

De http://docs.python.org/library/signal.html#module-signal :

S Signale und Threads in der gleichen Programm verwendet werden. Das Wichtigste ist bei der Verwendung von Signalen und Threads zu beachten gleichzeitig zu verwenden, ist: führen Sie immer signal()-Operationen im Haupt-Thread der Ausführung. Jeder Thread kann ein alarm(), getsignal(), pause(), setitimer() oder getitimer() ausführen; nur der Hauptthread kann ein neues Signal setzen Handler setzen, und der Hauptthread wird der der einzige sein, der Signale empfängt (dies wird durch das Python Signal Modul erzwungen, selbst wenn die zugrunde liegende Thread Implementierung das Senden von Signale an einzelne Threads zu senden). Diese [ ] Mittel der Inter-Thread-Kommunikation verwendet werden können. Verwenden Sie stattdessen Sperren.

Die zweite, von http://docs.python.org/library/thread.html#module-thread :

Threads interagieren auf merkwürdige Weise mit Interrupts: die KeyboardInterrupt-Ausnahme wird von einem beliebigen Thread empfangen. (Wenn das Signalmodul verfügbar ist, gehen Interrupts immer an den Hauptthread.)

EDIT : Es gab eine anständige Diskussion über die Mechanik der dies auf der Python Bug Tracker hier: http://bugs.python.org/issue1167930 . Natürlich endet es mit Guidos Worten: "Das wird wahrscheinlich nicht verschwinden, also müsst ihr einfach damit leben damit leben. Wie Sie herausgefunden haben, löst die Angabe einer Zeitüberschreitung das Problem (gewissermaßen)." YMMV :-)

6voto

phihag Punkte 261131

Jarret Hardie bereits erwähnt es : Nach Guido van Rossum gibt es ab sofort keinen besseren Weg mehr: Wie in der Dokumentation , join(None) Blöcke (und das bedeutet keine Signale). Die Alternative - ein Aufruf mit einem großen Timeout ( join(2**31) oder so) und Überprüfung isAlive sieht großartig aus. Die Art und Weise, wie Python mit Timern umgeht, ist jedoch katastrophal, wie man sieht, wenn man das Python-Testprogramm mit servth.join(100) anstelle von servth.join() :

select(0, NULL, NULL, NULL, {0, 1000})  = 0 (Timeout)
select(0, NULL, NULL, NULL, {0, 2000})  = 0 (Timeout)
select(0, NULL, NULL, NULL, {0, 4000})  = 0 (Timeout)
select(0, NULL, NULL, NULL, {0, 8000})  = 0 (Timeout)
select(0, NULL, NULL, NULL, {0, 16000}) = 0 (Timeout)
select(0, NULL, NULL, NULL, {0, 32000}) = 0 (Timeout)
select(0, NULL, NULL, NULL, {0, 50000}) = 0 (Timeout)
select(0, NULL, NULL, NULL, {0, 50000}) = 0 (Timeout)
select(0, NULL, NULL, NULL, {0, 50000}) = 0 (Timeout)
--- Skipped 15 equal lines ---
select(0, NULL, NULL, NULL, {0, 50000}Killing

D.h. Python wacht alle 50 ms auf, was dazu führt, dass eine einzige Anwendung die CPU vom Schlafen abhält.

3voto

Eli Bendersky Punkte 246100

Umfrage zu isAlive vor dem Aufruf join . Diese Abfrage kann natürlich unterbrochen werden, und sobald der Thread nicht mehr isAlive , join ist unmittelbar.

Eine Alternative wäre die Befragung zu join mit einer Zeitüberschreitung, Überprüfung mit isAlive ob die Zeitüberschreitung aufgetreten ist. Dies kann weniger CPU verbrauchen als die vorherige Methode.

1voto

Eric O Lebigot Punkte 85676

Soweit ich verstanden habe, wird eine ähnliche Frage gelöst in Das kleine Buch der Semaphoren (kostenloser Download), Anhang A Teil 3

0voto

Shabbyrobe Punkte 11820

Ich weiß, dass ich ein bisschen spät dran bin, aber ich bin zu dieser Frage gekommen, weil ich auf eine bessere Antwort hoffte, als mich mit einer Auszeit anzuschließen, was ich bereits tat. Am Ende habe ich mir etwas ausgedacht, das vielleicht eine schreckliche Bastardisierung von Signalen ist, aber es beinhaltet die Verwendung von signal.pause() anstelle von Thread.join() und signalisiert dem aktuellen Prozess, wenn der Thread das Ende seiner Ausführung erreicht hat:

import signal, os, time, sys, threading, random

threadcount = 200

threadlock = threading.Lock()
pid = os.getpid()
sigchld_count = 0

def handle_sigterm(signalnum, frame):
    print "SIGTERM"

def handle_sigchld(signalnum, frame):
    global sigchld_count
    sigchld_count += 1

def faux_join():
    global threadcount, threadlock
    threadlock.acquire()
    threadcount -= 1
    threadlock.release()
    os.kill(pid, signal.SIGCHLD)

def thread_doer():
    time.sleep(2+(2*random.random()))
    faux_join()

if __name__ == '__main__':
    signal.signal(signal.SIGCHLD, handle_sigchld)
    signal.signal(signal.SIGTERM, handle_sigterm)

    print pid
    for i in xrange(0, threadcount):
        t = threading.Thread(target=thread_doer)
        t.start()

    while 1:
        if threadcount == 0: break
        signal.pause()
        print "Signal unpaused, thread count %s" % threadcount

    print "All threads finished"
    print "SIGCHLD handler called %s times" % sigchld_count

Wenn Sie die SIGTERMs in Aktion sehen wollen, verlängern Sie die Länge der Schlafzeit in thread_doer und geben eine kill $pid Befehl von einem anderen Terminal aus, wobei $pid ist die pid id, die zu Beginn ausgegeben wird.

Ich poste dies in der Hoffnung, anderen zu helfen, und nicht, weil mir gesagt wird, dass das verrückt ist oder ein Fehler vorliegt. Ich bin mir nicht sicher, ob die Sperre für die Fadenzahl noch notwendig ist - ich habe sie am Anfang meiner Experimente eingebaut und dachte, ich sollte sie für alle Fälle drin lassen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X