2009-03-10 17 views
22

C'è un modo per attendere la fine di un thread, ma ancora intercettare i segnali?Unire thread interrompibile in Python

Si consideri il seguente C programma:

#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; 
} 

Si conclude dopo un secondo e stampe:

Killing 
Handling 10 

Al contrario, ecco il mio tentativo di scrivere in 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") 

Esso stampa:

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

Come faccio a farlo comportare come la versione C?

+0

'gcc -pthread thread.c' è il modo per compilare il sorgente C se qualcuno ha affrontato errori come provando 'gcc thread.c' da solo. – ViFI

risposta

5

Jarret Hardie già mentioned it: Secondo Guido van Rossum, non c'è modo migliore fin d'ora: Come indicato nelle documentation, join(None) blocchi (e questo significa che nessun segnale). L'alternativa: chiamare con un enorme timeout (join(2**31) o giù di lì) e controllare isAlive sembra ottima. Tuttavia, il modo in cui Python gestisce i timer è disastrosa, come si è visto quando si esegue il programma di test di pitone con servth.join(100) invece di 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 

Vale a dire, Python si sveglia ogni 50 ms, che porta a una singola applicazione mantenendo la CPU di dormire .

+0

In Python 3, 'servth.join()' bloccato su ['lock.acquire()'] (https://docs.python.org/3/library/_thread.html#_thread.lock.acquire) può essere interrotto da un segnale. – jfs

+0

sto postando per concordare con @phihag (in python 2.6 centos-6): mi aspettavo di ricevere segnali quando il mio thread principale fa un 'if thread.is_alive(): thread.join()' ... ma purtroppo io non ha ricevuto il segnale. stranamente quando faccio un 'while thread.is_alive(): thread.join (30.0)' Ricevo il segnale come previsto. -> ** in sintesi ho trovato lo stesso comportamento che ho trovato @phihag (cioè devi usare 'thread.join' con un timeout ... altrimenti non puoi ricevere i segnali). ** –

+0

ecco [un'altra risposta ] (https://stackoverflow.com/a/29661280/52074) che consente di utilizzare un join di thread con un timeout –

3

Sondaggio su isAlive prima di chiamare join. Questo polling può essere interrotto, ovviamente, e una volta che il thread non è isAlive, join è immediato.

Un'alternativa sarebbe il polling su join con un timeout, controllando con isAlive se si è verificato il timeout. Questo può spendere meno CPU rispetto al metodo precedente.

+1

Certo, il polling funziona, ma utilizza più risorse e impedisce lo stato di sospensione della CPU, che può essere piuttosto costoso per i notebook. Sto cercando un'altra soluzione. – phihag

+0

sì, ma utilizzando il secondo metodo non si spreca CPU, perché unire con i blocchi di timeout e rilasciarlo. quindi anche un timeout relativamente piccolo di poche dozzine di millisecondi ti lascerà il 99,9% di CPU libero –

+0

eliben: il 99,9% CPU gratuito non è per niente desiderabile se il lavoro viene distribuito in modo uniforme, preferirei l'80% senza CPU in un singolo burst per un'applicazione desktop. Vedi http://www.lesswatts.org/projects/applications-power-management/avoid-pulling.php per i dettagli. – phihag

14

I thread in Python sono animali un po 'strani dato il blocco dell'interprete globale. Potresti non essere in grado di ottenere ciò che desideri senza ricorrere a un timeout di join e isAlive come suggerisce Eliben.

Ci sono due punti nel documento che ne forniscono la ragione (e forse anche di più).

La prima:

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

Alcuni cura deve essere presa se vengono utilizzati sia segnali e thread nello stesso programma . La cosa fondamentale per ricordare utilizzando segnali e thread simultaneamente è: eseguire sempre operazioni signal() nella thread principale di esecuzione. Qualsiasi thread può eseguire un allarme(), otterignal(), pause(), setitimer() o getitimer(); solo il filo principale può impostare un nuovo gestore di segnale , e il filo principale sarà il solo a ricevere segnali (puo applicata dal modulo segnale Python , anche se il sottostante attuazione filo supporta l'invio segnali fili singoli). Questo significa che i segnali non possono essere utilizzati come mezzo di comunicazione inter-thread . Utilizzare i blocchi invece.

Il secondo, da http://docs.python.org/library/thread.html#module-thread:

discussioni interagiscono strano con gli interrupt: l'eccezione KeyboardInterrupt sarà ricevuto da un thread arbitrario. (Quando il modulo segnale è disponibile, interrompe andare sempre al thread principale.)

EDIT: C'è stata una discussione decente della meccanica di questo sul pitone bug tracker qui: http://bugs.python.org/issue1167930. Ovviamente, finisce con Guido che dice: "È improbabile che vada via, quindi con questo devi solo vivere . Come hai scoperto, specificare un timeout risolve il problema (sorta di)." YMMV :-)

+0

Bene, I * am * chiama signal.signal nella thread principale (1) e il modulo signal è disponibile (2). – phihag

+0

Giusto, ma il segnale andrà solo al thread principale, quindi è necessario attendere che il servth si unisca prima che il segnale arrivi al gestore di segnale (tramite il thread principale). Confondendo, no? –

0

So che sono un po 'in ritardo per la festa, ma sono giunto a questa domanda sperando in una risposta migliore rispetto a un timeout, che stavo già facendo. Alla fine ho cucinato qualcosa che può o non può essere un bastardisation orribile di segnali, ma comporta l'uso signal.pause() anziché Thread.join() e segnalare l'attuale processo quando il filo raggiunge la fine della sua esecuzione:

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 

Se vuoi vedere i SIGTERM in azione, estendere la durata del tempo di sonno in thread_doer ed emettere un comando da un altro terminale, dove $pid è l'ID del pid stampato all'inizio.

Inserisco questo messaggio nella speranza di aiutare gli altri a dire che questo è pazzo o ha un bug. Non sono sicuro che il lock-in del threadcount sia ancora necessario - l'ho messo lì all'inizio della mia sperimentazione e ho pensato di lasciarlo lì per caso.

1

Per quanto ho capito, una domanda simile è risolto in The Little Book of Semaphores (download gratuito), appendice A parte 3 ...

+0

Si tratta di un discreto compromesso, che sostituisce essenzialmente la comunicazione tra processi intra-processo – phihag