Обработка вызова функции блокировки в Python - PullRequest
4 голосов
/ 29 октября 2010

Я работаю с 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 вызываетFlow-Graph (который взаимодействует с оборудованием).Я застрял в этом.Я мог бы использовать поток, но я неопытный, как тайм-аут потоков в 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".;)

Спасибо, Мариус

Ответы [ 8 ]

6 голосов
/ 01 ноября 2010

Прежде всего - следует избегать использования сигналов любой ценой:

1) Это может привести к тупику.SIGALRM может дойти до процесса до системного вызова блокировки (представьте сверхвысокую нагрузку в системе!), И системный вызов не будет прерван.Тупик.

2) Игра с сигналами может иметь неприятные нелокальные последствия.Например, системные вызовы в других потоках могут прерываться, что обычно не то, что вам нужно.Обычно системные вызовы перезапускаются при получении (не смертельного) сигнала.Когда вы устанавливаете обработчик сигнала, он автоматически отключает это поведение для всего процесса или, так сказать, группы потоков.Проверьте 'man siginterrupt' на этом.

Поверьте мне - я встречал две проблемы раньше, и они совсем не веселые.

В некоторых случаях блокировку можно избежать явно - я настоятельно рекомендую использоватьselect () и friends (отметьте select module в Python), чтобы обрабатывать блокировку записи и чтения.Это не решит проблему блокировки вызова 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 вокруг них.

Когда вы не можете явно принудительно форсироватьнеблокирующее поведение, скажем, вы просто используете внешнюю библиотеку, тогда это будет намного сложнее.Потоки могут делать, но, очевидно, это не современное решение, как правило, просто неправильно.

Боюсь, что в целом вы не можете решить это надежным способом - это действительнозависит от того, ЧТО вы блокируете.

4 голосов
/ 29 октября 2010

IIUC, у каждого top_block есть метод stop.Таким образом, вы можете запустить top_block в потоке и выполнить остановку, если истекло время ожидания.Было бы лучше, если бы у wait () top_block также был тайм-аут, но, увы, это не так.

В основном потоке вам нужно ждать два случая:б) истекло время ожидания.Ожидания заняты - это зло :-), поэтому вы должны использовать время ожидания соединения для ожидания потока.Если поток еще жив после объединения, вам нужно остановить top_run.

2 голосов
/ 06 ноября 2010

Легкая часть вашего вопроса связана с обработкой сигналов.С точки зрения среды выполнения Python, сигнал, который был получен во время выполнения системным вызовом интерпретатором, представляется вашему коду Python как исключение OSError с атрибутом 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() для вашего управляющего объекта (tb?).Я предполагаю, что это переводит представление состояния 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() и получает доступ к атрибутам, о которых я ничего не знаю, а затем вызывает его .unlock() перед повторным сном.

2 голосов
/ 29 октября 2010

Вы можете установить сигнал будильника, который прервет ваш звонок с таймаутом:

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)

EDIT: Что не так с этим куском кода? Это не должно прерывать ваше приложение:

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. Получение сигнала обычно прерывает системные вызовы (затем разблокирует ваше приложение)

1 голос
/ 05 ноября 2010
if not seq1_sent:
        tb = send_seq_2.top_block()
        tb.Run(True)
        seq1_sent = True
        if time.time() < endtime:
            break

Если 'if time.time () endtime' в этомпроверить

0 голосов
/ 04 ноября 2010

Вы упоминаете уничтожение потоков в Python - это частично возможно, хотя вы можете убить / прервать другой поток только при запуске кода Python, а не в коде C, так что это может помочь вам не так, как вы хотите.

этот ответ на другой вопрос: python: как отправлять пакеты в многопоточном режиме, а затем поток уничтожает себя

или Google для уничтожаемых потоков Python для получения более подробной информации, например: http://code.activestate.com/recipes/496960-thread2-killable-threads/

0 голосов
/ 03 ноября 2010

вы можете попробовать использовать отложенное выполнение ... Twisted Framework использует их много

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

0 голосов
/ 29 октября 2010

Если вы хотите установить тайм-аут для функции блокировки, threading.Thread как метод join (timeout), который блокирует до времени ожидания.

В принципе, что-то подобное должно делать то, что вы хотите:

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