Планирование вопросов в Python - PullRequest
0 голосов
/ 30 июля 2009

Я использую python для сопряжения аппаратного устройства USB-анализатора с API-интерфейсом Python, предоставленным поставщиком, и пытаюсь читать (пакеты USB) с устройства в отдельном потоке в бесконечном цикле (который работает нормально) , Проблема в том, что мой основной цикл, кажется, никогда не запланирован снова (мой цикл чтения привлекает все внимание).

Код выглядит примерно так:

from threading import Thread
import time
usb_device = 0

def usb_dump(usb_device):
    while True:
        #time.sleep(0.001)
        packet = ReadUSBDevice(usb_device)
        print "packet pid: %s" % packet.pid

class DumpThread(Thread):
    def run(self):
        usb_dump()

usb_device = OpenUSBDevice()
t = DumpThread()
t.start()
print "Sleep 1"
time.sleep(1)
print "End"
CloseUSBDevice(usb_device)
sys.exit(0)

(я мог бы вставить настоящий код, но так как вам нужно аппаратное устройство, я думаю, это не сильно поможет).

Я ожидаю, что этот код начнет сбрасывать usb-пакеты примерно за секунду до того, как основной поток завершит всю программу. Тем не менее, все, что я вижу, это «Сон 1», а затем usb_dump() процедура выполняется вечно. Если я раскомментирую оператор time.sleep (0.001) во внутреннем цикле процедуры usb_dump(), все начнет работать так, как я ожидаю, но тогда код python не сможет успевать за всеми поступающими пакетами :-(

Поставщик говорит мне, что это проблема с планировщиком Python, а не ошибка его API, и поэтому мне это не поможет:

«Однако, похоже, что вы испытываете некоторые нюансы при использовании многопоточности в Python. Помещая time.sleep в поток DumpThread, вы явно даете понять системе потоков Python, что нужно отказаться от контроля. В противном случае интерпретатор Python решает, когда переключать потоки, и обычно делает это после выполнения определенного количества инструкций байтового кода. »

Может кто-нибудь подтвердить, что проблема в питоне? Есть ли другой способ сделать управление выпуском DumpThread? Есть еще идеи?

Ответы [ 3 ]

3 голосов
/ 30 июля 2009

Ваш поставщик был бы прав, если бы у вас был чистый питон код; однако расширения C могут освобождать GIL , и, следовательно, допускают фактическую многопоточность.

В частности, time.sleep делает освобождение GIL (вы можете проверить это непосредственно из исходного кода, здесь - посмотрите на реализацию floatsleep); поэтому у вашего кода не должно быть никаких проблем. В качестве дополнительного доказательства я также сделал простой тест, просто удалив вызовы на USB, и он на самом деле работает, как и ожидалось:

from threading import Thread
import time
import sys

usb_device = 0

def usb_dump():
    for i in range(100):
        time.sleep(0.001)
        print "dumping usb"

class DumpThread(Thread):
    def run(self):
        usb_dump()

t = DumpThread()
t.start()
print "Sleep 1"
time.sleep(1)
print "End"
sys.exit(0)

Наконец, всего пара замечаний по коду, который вы разместили:

  • usb_device не передается в поток. Вам нужно передать его в качестве параметра или (argh!) Указать потоку получить его из глобального пространства имен.
  • Вместо принудительного вызова sys.exit () может быть лучше просто дать сигнал потоку остановиться, а затем закрыть устройство USB. Я подозреваю, что ваш код может вызвать проблемы с многопоточностью, как сейчас.
  • Если вам нужен только периодический опрос, то Threading.Timer может быть лучшим решением для вас.

[ Обновление ] О последнем пункте: как сказано в комментарии, я думаю, что Timer лучше соответствовал бы семантике вашей функции (периодический опрос) и автоматически избегал бы проблем с GIL не выпускается кодом поставщика.

2 голосов
/ 30 июля 2009

Я предполагаю, что вы написали модуль Python C, который предоставляет функцию ReadUSBDevice и что он предназначен для блокировки до получения USB-пакета, а затем возвращает его.

Собственная реализация ReadUSBDevice должна выпустить Python GIL, пока он ожидает пакет USB, а затем повторно получить его, когда он получит его. Это позволяет другим потокам Python работать во время выполнения собственного кода.

http://docs.python.org/c-api/init.html#thread-state-and-the-global-interpreter-lock

Пока вы разблокировали GIL, вы не можете получить доступ к Python. Отпустите GIL, запустите функцию блокировки, затем, когда вы узнаете, что у вас есть что-то, чтобы вернуться обратно в Python, повторно запустите его.

Если вы этого не сделаете, никакие другие потоки Python не смогут выполняться, пока идет ваша собственная блокировка. Если это модуль Python, поставляемый поставщиком, сбой при освобождении GIL во время действий по собственной блокировке является ошибкой.

Обратите внимание, что если вы получаете много пакетов и фактически обрабатываете их в Python, другие потоки все равно должны работать. Несколько потоков, которые на самом деле выполняют код Python, не будут работать параллельно, но они часто будут переключаться между потоками, давая им всем шанс на запуск. Это не работает, если собственный код блокируется без освобождения GIL.

edit: я вижу, вы упомянули, что это библиотека, поставляемая поставщиком. Если у вас нет источника, быстрый способ узнать, выпускают ли они GIL: запустите поток ReadUSBDevice, пока не происходит никаких действий с USB, поэтому ReadUSBDevice просто сидит и ждет данных. Если они выпускают GIL, другие потоки должны работать беспрепятственно. Если это не так, он заблокирует весь переводчик. Это было бы серьезной ошибкой.

0 голосов
/ 30 июля 2009

Я думаю, что продавец правильный. Предполагая, что это CPython, нет истинного параллельного потока; только один поток может выполняться одновременно. Это связано с реализацией глобальной блокировки интерпретатора .

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

Другая возможность, которая может помочь, - это изменение поведения переключателя планировщика 1010 *.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...