python Python中的可中断线程连接

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/631441/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 20:30:42  来源:igfitidea点击:

Interruptible thread join in Python

pythonmultithreading

提问by phihag

Is there any way to wait for termination of a thread, but still intercept signals?

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

Consider the following Cprogram:

考虑以下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;
}

It ends after one second and prints:

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

Killing
Handling 10

In contrast, here's my attempt to write it in Python:

相比之下,这是我用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")

It prints:

它打印:

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

How do I make it behave like the C version?

我如何让它表现得像 C 版本?

采纳答案by phihag

Jarret Hardie already mentioned it: According to Guido van Rossum, there's no better way as of now: As stated in the documentation, join(None)blocks (and that means no signals). The alternative - calling with a huge timeout (join(2**31)or so) and checking isAlivelooks great. However, the way Python handles timers is disastrous, as seen when running the python test program with servth.join(100)instead of servth.join():

Jarret Hardie 已经提到过:根据Guido van Rossum 的说法,目前没有更好的方法:如文档中所述,join(None)阻塞(这意味着没有信号)。另一种选择 - 以巨大的超时(join(2**31)左右)调用并检查isAlive看起来很棒。然而,Python 处理计时器的方式是灾难性的,正如在运行 python 测试程序时看到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

I.e., Python wakes up every 50 ms, leading to a single application keeping the CPU from sleeping.

即,Python 每 50 毫秒唤醒一次,导致单个应用程序使 CPU 无法休眠。

回答by Jarret Hardie

Threads in Python are somewhat strange beasts given the global interpreter lock. You may not be able to achieve what you want without resorting to a join timeout and isAlive as eliben suggests.

考虑到全局解释器锁,Python 中的线程有点奇怪。如果不求助于 eliben 建议的加入超时和 isAlive,您可能无法实现您想要的。

There are two spots in the docs that give the reason for this (and possibly more).

文档中有两个地方给出了这个原因(可能还有更多)。

The first:

首先:

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

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

Some care must be taken if both signals and threads are used in the same program. The fundamental thing to remember in using signals and threads simultaneously is: always perform signal() operations in the main thread of execution. Any thread can perform an alarm(), getsignal(), pause(), setitimer() or getitimer(); only the main thread can set a new signal handler, and the main thread will be the only one to receive signals (this is enforced by the Python signal module, even if the underlying thread implementation supports sending signals to individual threads). This means that signals can't be used as a means of inter-thread communication. Use locks instead.

如果在同一程序中同时使用信号和线程,则必须小心。同时使用信号和线程要记住的基本事情是:始终在执行的主线程中执行 signal() 操作。任何线程都可以执行alarm()、getsignal()、pause()、setitimer() 或getitimer();只有主线程可以设置新的信号处理程序,并且主线程将是唯一接收信号的线程(这是由 Python 信号模块强制执行的,即使底层线程实现支持向单个线程发送信号)。这意味着信号不能用作线程间通信的手段。使用锁代替。

The second, from http://docs.python.org/library/thread.html#module-thread:

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

Threads interact strangely with interrupts: the KeyboardInterrupt exception will be received by an arbitrary thread. (When the signal module is available, interrupts always go to the main thread.)

线程与中断进行了奇怪的交互:KeyboardInterrupt 异常将被任意线程接收。(当信号模块可用时,中断总是转到主线程。)

EDIT:There was a decent discussion of the mechanics of this on the python bug tracker here: http://bugs.python.org/issue1167930. Of course, it ends with Guido saying: " That's unlikely to go away, so you'll just have to live with this. As you've discovered, specifying a timeout solves the issue (sort of)." YMMV :-)

编辑:这里在 python 错误跟踪器上对此机制进行了不错的讨论:http: //bugs.python.org/issue1167930。当然,结尾是 Guido 说:“这不太可能消失,所以你只能忍受这个。正如你所发现的,指定超时解决了这个问题(有点)。” YMMV :-)

回答by Eli Bendersky

Poll on isAlivebefore calling join. This polling can be interrupted, of course, and once the thread isn't isAlive, joinis immediate.

isAlive打电话前先投票join。当然,这个轮询可以被中断,一旦线程不是isAlivejoin就会立即中断。

An alternative would be polling on joinwith a timeout, checking with isAlivewhether the timeout occurred. This can spend less CPU than the previous method.

另一种方法是轮询join超时,检查isAlive是否发生超时。与前一种方法相比,这可以花费更少的 CPU。

回答by Eric O Lebigot

As far as I understand, a similar question is solved in The Little Book of Semaphores(free download), appendix A part 3…

据我了解,信号量小书(免费下载),附录 A 第 3 部分中解决了类似的问题……

回答by Shabbyrobe

I know I'm a bit late to the party, but I came to this question hoping for a better answer than joining with a timeout, which I was already doing. In the end I cooked something up that may or may not be a horrible bastardisation of signals, but it involves using signal.pause()instead of Thread.join()and signalling the current process when the thread reaches the end of its execution:

我知道我参加聚会有点晚了,但我来到这个问题是希望得到一个比暂停加入更好的答案,我已经这样做了。最后,我做了一些可能是也可能不是信号的可怕混蛋的事情,但它涉及在线程到达其执行结束时使用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

If you want to see the SIGTERMs in action, extend the length of the sleep time in thread_doerand issue a kill $pidcommand from another terminal, where $pidis the pid id printed at the start.

如果您想查看 SIGTERM 的运行情况,请延长睡眠时间的长度thread_doerkill $pid从另一个终端发出命令,其中$pidpid id 是在开始时打印的。

I post this as much in the hope of helping others as being told that this is crazy or has a bug. I'm not sure if the lock on threadcount is still necessary - I put it in there early in my experimentation and thought I should leave it in there in case.

我发布此内容是为了帮助其他人,因为我被告知这很疯狂或有错误。我不确定是否仍然需要锁定线程数 - 我在实验的早期就把它放在那里,并认为我应该把它留在那里以防万一。