代码之家  ›  专栏  ›  技术社区  ›  phihag

Python中的可中断线程连接

  •  22
  • phihag  · 技术社区  · 15 年前

    有没有办法等待线程终止,但仍然截获信号?

    考虑以下事项 C

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

    它在一秒钟后结束并打印:

    Killing
    Handling 10
    

    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")
    

    它打印:

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

    如何使其表现得像C版本?

    5 回复  |  直到 15 年前
        1
  •  15
  •   Jarret Hardie    15 年前

    Python中的线程在全局解释器锁定的情况下有些奇怪。如果不诉诸加入超时,您可能无法实现您想要的,并且正如eliben所建议的那样。

    文档中有两个点给出了原因(可能更多)。

    第一:

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

    如果两者都存在,则必须小心 信号和线程用于 记住在使用信号和线程时 同时是:始终执行 主线程中的signal()操作 执行的方式。任何线程都可以执行 报警()、获取信号()、暂停(), setitimer()或getitimer();只有 主线程可以设置一个新的信号 处理程序,并且主线程将 由Python信号强制执行 模块,即使底层线程 实现支持发送 向单个线程发送信号)。这 意味着信号不能用作 线程间通信的方式。 用锁代替。

    第二个,来自 http://docs.python.org/library/thread.html#module-thread

    线程与中断进行奇怪的交互:键盘中断异常 由任意线程接收。(当信号模块可用时,中断 始终转到主线程。)

    编辑: 在python bug跟踪器上,对这一机制进行了详细的讨论: http://bugs.python.org/issue1167930 . 当然,它的结尾是Guido说:“这不太可能消失,所以你只能活下去 用这个。正如您所发现的,指定超时可以解决问题 (有点)。“YMMV:-)

        2
  •  6
  •   Community George Stocker    7 年前

    Jarret Hardie已经来了 mentioned it Guido van Rossum documentation , join(None) 阻塞(这意味着没有信号)。另一种选择是使用巨大的超时调用( join(2**31) isAlive servth.join(100) 而不是 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
    

    也就是说,Python每50毫秒唤醒一次,导致单个应用程序阻止CPU睡眠。

        3
  •  3
  •   Eli Bendersky    15 年前

    投票表决 isAlive 打电话之前 join 我活着 参加 这是立即的。

    参加 有一个超时,正在检查 是否发生超时。这可以比前一种方法花费更少的CPU。

        4
  •  1
  •   Eric O. Lebigot    13 年前

    The Little Book of Semaphores (免费下载),附录A第3部分

        5
  •  0
  •   Shabbyrobe    13 年前

    我知道我参加聚会有点晚了,但我来问这个问题是希望得到一个更好的答案,而不是加入一个我已经在做的暂停。最后,我编造了一些东西,可能是,也可能不是一个可怕的信号堡垒,但它涉及到使用 signal.pause() 而不是 Thread.join() 以及在线程到达其执行结束时向当前进程发送信号:

    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
    

    thread_doer 并发出 kill $pid 来自另一个终端的命令,其中 $pid

    我发布这篇文章的目的是希望帮助别人,就像我被告知这是疯狂的或者有bug一样。我不确定threadcount上的锁是否仍然有必要——我在实验的早期就把它放在那里了,我想我应该把它留在那里以防万一。