在Python中处理阻塞函数调用

时间:2010-10-29 11:14:52

标签: python multithreading blocking gnuradio

我正在使用Gnuradio framework。我处理我生成的流程图来发送/接收信号。这些流程图初始化并启动,但它们不会将控制流程返回给我的应用程序:

我导入了time

while time.time() < endtime:
        # invoke GRC flowgraph for 1st sequence
        if not seq1_sent:
            tb = send_seq_2.top_block()
            tb.Run(True)
            seq1_sent = True
            if time.time() < endtime:
                break

        # invoke GRC flowgraph for 2nd sequence
        if not seq2_sent:
            tb = send_seq_2.top_block()
            tb.Run(True)
            seq2_sent = True
            if time.time() < endtime:
                break

问题是:只有第一个if语句调用流图(与硬件交互)。我陷入了困境。我可以使用一个Thread,但我没有经验,如何在Python中超时线程。我怀疑这是可能的,因为似乎杀死线程不在API中。这个脚本只需要在Linux上运行......

如何正确处理Python的阻塞函数 - 而不会破坏整个程序。 这个问题的另一个更具体的例子是:

import signal, os

def handler(signum, frame):
        # print 'Signal handler called with signal', signum
        #raise IOError("Couldn't open device!")
        import time
        print "wait"
        time.sleep(3)


def foo():
    # Set the signal handler and a 5-second alarm
    signal.signal(signal.SIGALRM, handler)
    signal.alarm(3)

    # This open() may hang indefinitely
    fd = os.open('/dev/ttys0', os.O_RDWR)
    signal.alarm(0)          # Disable the alarm


foo()
print "hallo"

我如何获得print "hallo"。 ;)

谢谢, 的Marius

8 个答案:

答案 0 :(得分:6)

首先 - 应该不惜一切代价避免使用信号:

1)可能导致死锁。 SIGALRM可能会在阻塞系统调用之前到达进程(想象系统中的超高负载!)并且系统调用不会被中断。死锁。

2)玩信号可能会产生一些令人讨厌的非本地后果。例如,其他线程中的系统调用可能会被中断,这通常不是您想要的。通常,当收到(不是致命的)信号时,系统调用会重新启动。当你设置一个信号处理程序时,它会自动关闭整个进程或线程组的这种行为。检查'man siginterrupt'就可以了。

相信我 - 我之前遇到过两个问题,而且根本不是很有趣。

在某些情况下,可以明确地避免阻塞 - 我强烈建议使用select()和friends(检查Python中的select模块)来处理阻塞写入和读取。但这并不能解决阻塞open()调用问题。

为此,我测试了这个解决方案,它适用于命名管道。它以非阻塞方式打开,然后将其关闭并使用select()调用,如果没有可用的话,最终会超时。

import sys, os, select, fcntl

f = os.open(sys.argv[1], os.O_RDONLY | os.O_NONBLOCK)

flags = fcntl.fcntl(f, fcntl.F_GETFL, 0)
fcntl.fcntl(f, fcntl.F_SETFL, flags & ~os.O_NONBLOCK)

r, w, e = select.select([f], [], [], 2.0)

if r == [f]:
    print 'ready'
    print os.read(f, 100)
else:
    print 'unready'

os.close(f)

用以下方法测试:

mkfifo /tmp/fifo
python <code_above.py> /tmp/fifo (1st terminal)
echo abcd > /tmp/fifo (2nd terminal)

通过一些额外的努力,select()调用可以用作整个程序的主循环,聚合所有事件 - 你可以使用libev或libevent,或者围绕它们使用一些Python包装。

当你无法明确强制非阻塞行为时,假设你只是使用外部库,那么它将会变得更加困难。线程可能会这样做,但显然它不是最先进的解决方案,通常是错误的。

我担心一般情况下你无法以强有力的方式解决这个问题 - 这实际上取决于你阻止的内容。

答案 1 :(得分:4)

IIUC,每个top_block都有一个停止方法。所以你实际上可以在一个线程中运行top_block,并在超时到达时发出一个停止。如果top_block的wait()也有一个超时会更好,但唉,它没有。

在主线程中,您需要等待两种情况:a)top_block完成,b)超时到期。忙等待是邪恶的:-),所以你应该使用线程的join-with-timeout来等待线程。如果线程在连接后仍处于活动状态,则需要停止top_run。

答案 2 :(得分:2)

您可以设置信号警报,以便在超时时中断您的呼叫:

http://docs.python.org/library/signal.html

signal.alarm(1) # 1 second

my_blocking_call()
signal.alarm(0)

如果您想确保它不会破坏您的应用程序,您也可以设置信号处理程序:

def my_handler(signum, frame):
    pass

signal.signal(signal.SIGALRM, my_handler)

修改 这段代码有什么问题?这不应该中止您的申请:

import signal, time

def handler(signum, frame):
    print "Timed-out"

def foo():
    # Set the signal handler and a 5-second alarm
    signal.signal(signal.SIGALRM, handler)
    signal.alarm(3)

    # This open() may hang indefinitely
    time.sleep(5)
    signal.alarm(0)          # Disable the alarm


foo()
print "hallo"

事情是:

  1. SIGALRM的默认处理程序是中止应用程序,如果您设置了处理程序,那么它应该不再停止该应用程序。

  2. 接收信号通常会中断系统调用(然后取消阻止您的应用程序)

答案 3 :(得分:2)

问题的简单部分与信号处理有关。从Python运行时的角度来看,在解释器进行系统调用时收到的信号作为OSError异常呈现给您的Python代码,其中errno归因于errno.EINTR

所以这可能与你想要的大致相同:

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

    def handler(signum, frame):
            # print 'Signal handler called with signal', signum
            #raise IOError("Couldn't open device!")
            print "timed out"
            time.sleep(3)


    def foo():
        # Set the signal handler and a 5-second alarm
        signal.signal(signal.SIGALRM, handler)

        try:
            signal.alarm(3)
            # This open() may hang indefinitely
            fd = os.open('/dev/ttys0', os.O_RDWR)
        except OSError, e:
            if e.errno != errno.EINTR:
                raise e
        signal.alarm(0)          # Disable the alarm

    foo()
    print "hallo"

注意我已经将time的导入移出了函数定义,因为以这种方式隐藏导入似乎是不好的形式。我一点也不清楚为什么你在信号处理程序中睡觉,事实上,这似乎是一个相当糟糕的主意。

我想要做的关键点是任何(非忽略的)信号都会中断你的Python代码执行主线。您的处理程序将使用参数指示哪个信号编号触发执行(允许一个Python函数用于处理许多不同的信号)和一个框架对象(可用于某种调试或检测)。

由于代码的主要流程被中断,因此您必须将该代码包装在某些异常处理中,以便在发生此类事件后重新获得控制权。 (顺便提一下,如果你在C中编写代码,你就会有同样的问题;你必须为任何具有底层系统调用的库函数做好准备,以返回错误并在系统中处理-EINTR errno 通过循环返回重试或分支到主线中的某个替代(例如继续进行其他文件,或没有任何文件/输入等)。

正如其他人在回答您的问题时指出的那样,基于SIGALARM的方法很可能充满了可移植性和可靠性问题。更糟糕的是,其中一些问题可能是您在测试环境中永远不会遇到的竞争条件,并且可能仅在极难重现的条件下发生。丑陋的细节往往是在重新进入的情况下 - 如果在执行信号处理程序期间调度信号会发生什么?

我在一些脚本中使用了SIGALARM,在Linux下它对我来说不是问题。我正在处理的代码适合于该任务。它可能足以满足您的需求。

如果不了解Gnuradio代码的行为方式,从中实例化的对象类型以及返回的对象类型,您的主要问题很难回答。

浏览一下您链接的文档,我发现它们似乎没有提供任何可用于直接限制阻塞行为的“超时”参数或设置。在“控制流图”下的表中,我看到他们明确地说.run()可以无限期地执行或者直到收到SIGINT。我还注意到.start()可以启动应用程序中的线程,并且看起来会在运行时将控制权返回到Python代码行。 (这似乎取决于流程图的性质,我对此并不充分了解。)

听起来你可以创建流程图,.start(),然后(在Python代码的主线上处理或睡眠一段时间后)调用控制对象上的.lock()方法(结核病?)。我猜,这会将状态的Python表示...... Python对象......放入静止模式,以允许您查询状态,或者正如他们所说,重新配置流程图。如果您致电.run(),则会在致电.wait()后致电.start();并且.wait()显然会一直运行,直到所有块“表明它们已完成”或直到您调用对象的.stop()方法。

所以听起来你想使用.start()而不是.run().wait();然后在进行任何其他处理(包括.stop())后调用time.sleep()

也许就像这样简单:

    tb = send_seq_2.top_block()
    tb.start()
    time.sleep(endtime - time.time())
    tb.stop()
    seq1_sent = True
    tb = send_seq_2.top_block()
    tb.start()
    seq2_sent = True

..虽然我怀疑我的time.sleep()。也许你想在查询tb对象的状态时执行其他操作(可能需要在较小的时间间隔内休眠,调用其.lock()方法,并访问我一无所知的属性,然后调用它{{1再次睡觉之前。

答案 4 :(得分:1)

if not seq1_sent:
        tb = send_seq_2.top_block()
        tb.Run(True)
        seq1_sent = True
        if time.time() < endtime:
            break

如果'if time.time()&lt;结束时间:'那么你将突破循环,seq2_sent的东西永远不会被击中,也许你的意思是'time.time()&gt;在该测试中的“结束时间”?

答案 5 :(得分:0)

您可以尝试使用延迟执行...扭曲的框架使用它们很多

http://www6.uniovi.es/python/pycon/papers/deferex/

答案 6 :(得分:0)

你提到在Python中杀死线程 - 虽然只有在Python代码运行时才能杀死/中断另一个线程,而不是在C代码中,这是可能的,但这可能对你没有帮助。

看到另一个问题的答案: python: how to send packets in multi thread and then the thread kill itself

或google for killable python threads获取更多详细信息,如下所示: http://code.activestate.com/recipes/496960-thread2-killable-threads/

答案 7 :(得分:-1)

如果要在阻塞函数上设置超时,则将threading.Thread作为方法join(timeout)阻塞,直到超时为止。

基本上,类似的东西应该做你想要的:

import threading
my_thread = threading.Thread(target=send_seq_2.top_block)
my_thread.start()
my_thread.join(TIMEOUT)