Сделайте мой код ручным в вызовах фоновых функций, выполнение которых занимает много времени - PullRequest
2 голосов
/ 16 июня 2009

Некоторые функции в моем коде возвращаются долго. Мне не нужно возвращаемое значение, и я хотел бы выполнить следующие строки кода в сценарии, прежде чем медленная функция вернется. Точнее говоря, функции отправляют команды через USB в другую систему (через библиотеку C ++ с SWIG), и когда другая система завершает задачу, она возвращает значение «ОК». Я воспроизвел проблему в следующем примере. Как сделать так, чтобы "tic" и "toc" печатались один за другим без задержки? Я полагаю, что решение включает в себя потоки, но я не слишком знаком с ними. Может кто-нибудь показать мне простой способ решения этой проблемы?

from math import sqrt
from time import sleep

def longcalc():
    total = 1e6
    for i in range(total):
        r = sqrt(i)
    return r

def longtime():
    #Do stuff here
    sleep(1)
    return "sleep done"

print "tic"
longcalc()
print "toc"
longtime()
print "tic"

Ответы [ 3 ]

4 голосов
/ 16 июня 2009

Если код SWIGged C ++ специально не настроен для освобождения GIL (Global Interpreter Lock) до длительных задержек и повторного получения его до возвращения в Python, многопоточность может оказаться не очень полезной на практике. Вы можете попробовать мультипроцессор вместо:

from multiprocessing import Process

if __name__ == '__main__':
    print "tic"
    Process(target=longcalc).start()
    print "toc"
    Process(target=longtime).start()
    print "tic"

многопроцессорная обработка в стандартной библиотеке Python 2.6 и более поздних версий, но ее можно отдельно загрузить и установить для версий 2.5 и 2.4.

Редактировать: спрашивающий, конечно, пытается сделать что-то более сложное, чем это, и в комментарии объясняет: "" "Я получаю кучу ошибок, оканчивающихся на: "pickle.PicklingError: Can't pickle <type 'PySwigObject'>: it's not found as __builtin__.PySwigObject". Это можно решить без реорганизации всего моего кода? Процесс был вызван изнутри метода, привязанного к кнопке моего интерфейса wxPython." ""

multiprocessing нужно выбирать объекты для пересечения границ процесса; не уверен, какой именно SWIGged объект здесь задействован, но, если вы не можете найти способ сериализации и десериализации его и зарегистрировать это с помощью copy_reg module, вам нужно избегать его передачи через границу (сделать объекты SWIGged принадлежащими и используемыми один процесс, не имеющий их в качестве глобальных модулей, особенно в __main__, взаимодействует между процессами с Queue.Queue через объекты, которые не содержат SWIGged-объекты и т. д.).

Ошибки ранние (если они отличаются от тех, о которых вы сообщаете, что они "заканчиваются") на самом деле могут быть более значительными, но я не могу догадаться, не увидев их.

1 голос
/ 16 июня 2009
from threading import Thread
# ... your code    

calcthread = Thread(target=longcalc)
timethread = Thread(target=longtime)

print "tic"
calcthread.start()
print "toc"
timethread.start()
print "tic"

Посмотрите python threading docs для получения дополнительной информации о многопоточности в python.

Слово предупреждения о многопоточности: это может быть сложно. Очень сложно. Отладка многопоточного программного обеспечения может привести к худшим результатам, которые вы когда-либо испытывали как разработчик программного обеспечения.

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

0 голосов
/ 17 июня 2009

Вы можете использовать Future, который не входит в стандартную библиотеку, но очень прост в реализации:

from threading import Thread, Event

class Future(object):
    def __init__(self, thunk):
        self._thunk = thunk
        self._event = Event()
        self._result = None
        self._failed = None
        Thread(target=self._run).start()

    def _run(self):
        try:
            self._result = self._thunk()
        except Exception, e:
            self._failed = True
            self._result = e
        else:
            self._failed = False
        self._event.set()

    def wait(self):
        self._event.wait()
        if self._failed:
            raise self._result
        else:
            return self._result

Вы бы использовали эту конкретную реализацию следующим образом:

import time

def work():
    for x in range(3):
        time.sleep(1)
        print 'Tick...'
    print 'Done!'
    return 'Result!'

def main():
    print 'Starting up...'
    f = Future(work)
    print 'Doing more main thread work...'
    time.sleep(1.5)
    print 'Now waiting...'
    print 'Got result: %s' % f.wait()

К сожалению, при использовании системы, в которой нет «основного» потока, трудно сказать, когда вызывать «wait»; очевидно, вы не хотите останавливать обработку, пока вам не понадобится ответ.

В Twisted вы можете использовать deferToThread, что позволяет вам вернуться в основной цикл. Идиоматически эквивалентный код в Twisted будет выглядеть примерно так:

import time

from twisted.internet import reactor
from twisted.internet.task import deferLater
from twisted.internet.threads import deferToThread
from twisted.internet.defer import inlineCallbacks

def work():
    for x in range(3):
        time.sleep(1)
        print 'Tick...'
    print 'Done!'
    return 'Result!'

@inlineCallbacks
def main():
    print 'Starting up...'
    d = deferToThread(work)
    print 'Doing more main thread work...'
    yield deferLater(reactor, 1.5, lambda : None)
    print "Now 'waiting'..."
    print 'Got result: %s' % (yield d)

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

reactor.callWhenRunning(
    lambda : main().addCallback(lambda _: reactor.stop()))
reactor.run()

Основное отличие от Twisted заключается в том, что если в главном потоке происходит больше «вещей» - другие синхронизированные события запускаются, другие сетевые соединения получают трафик, кнопки нажимаются в графическом интерфейсе - эта работа будет происходить без проблем, поскольку deferLater и yield d на самом деле не останавливает весь поток, они только приостанавливают "главную" inlineCallbacks сопрограмму.

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