Python中的可中断线程连接

2024-06-16 07:57:40 发布

您现在位置:Python中文网/ 问答频道 /正文

是否有方法等待线程终止,但仍截获信号?

考虑以下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版本?


Tags: signalserverincludesleepthreadframenullkill
3条回答

在调用join之前对isAlive进行轮询。当然,这种轮询可以被中断,一旦线程没有isAlive,就可以立即join

另一种方法是使用超时在join上轮询,使用isAlive检查是否发生超时。这会比以前的方法花费更少的CPU。

考虑到全局解释器锁,Python中的线程有些奇怪。如果不按照eliben的建议使用连接超时和isAlive,您可能无法实现所需的功能。

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

第一个:

来自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.

第二个,来自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.)

编辑:在python bug tracker上有一个关于这个机制的不错的讨论:http://bugs.python.org/issue1167930。当然,最后圭多说:“那不太可能消失,所以你只能活着 带着这个。正如您所发现的,指定超时可以解决问题 (差不多)

Jarret Hardie已经mentioned it:根据Guido van Rossum,目前没有更好的方法:如documentationjoin(None)块中所述(这意味着没有信号)。另一种方法是用一个巨大的超时(join(2**31))调用并检查isAlive看起来很棒。然而,Python处理计时器的方式是灾难性的,在使用servth.join(100)而不是servth.join()运行Python测试程序时可以看到:

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休眠。

相关问题 更多 >