Python - Как я могу сделать этот код асинхронным? - PullRequest
9 голосов
/ 11 февраля 2011

Вот код, иллюстрирующий мою проблему:

def blocking1():
    while True:
        yield 'first blocking function example'

def blocking2():
    while True:
        yield 'second blocking function example'

for i in blocking1():
    print 'this will be shown'

for i in blocking2():
    print 'this will not be shown'

У меня есть две функции, которые содержат петли while True. Это даст данные, которые я затем буду где-то записывать (скорее всего, в базу данных sqlite).

Я поиграл с многопоточностью и начал работать. Однако мне это не очень нравится ... То, что я хотел бы сделать, это сделать мои функции блокировки асинхронными. Что-то вроде:

def blocking1(callback):
    while True:
        callback('first blocking function example')

def blocking2(callback):
    while True:
        callback('second blocking function example')

def log(data):
    print data

blocking1(log)
blocking2(log)

Как мне добиться этого в Python? Я видел, что стандартная библиотека поставляется с asyncore, и в этой игре большое имя - Twisted, но оба они, похоже, используются для сокетного ввода-вывода.

Как я могу асинхронизировать функции блокировки, не связанные с сокетом?

Ответы [ 5 ]

29 голосов
/ 12 февраля 2011

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

Вы просите нас сделать ваши функции блокировки неблокирующими. Однако - если вы не пишете операционную систему - у вас нет функций блокировки. У вас могут быть функции, которые блокируют, потому что они выполняют вызовы, блокирующие системные вызовы, или у вас могут быть функции, которые «блокируют», потому что они выполняют много вычислений.

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

Ответ на этот вопрос - очень длинная программа на Python и множество объяснений различных интерфейсов ОС и того, как они работают, но, к счастью, я уже написал этот ответ на другом сайте; Я назвал это Twisted . Если ваша конкретная задача уже поддерживается с помощью витого реактора , то вам повезло. В противном случае, если ваша задача соответствует какой-либо существующей концепции операционной системы, вы можете расширить реактор для ее поддержки. Практически говоря, есть только 2 из этих механизмов: файловые дескрипторы в каждой разумной операционной системе и порты завершения ввода / вывода в Windows.

В другом случае, если ваши функции потребляют много ресурсов ЦП и, следовательно, не возвращаются, они на самом деле не блокируются; ваш процесс все еще затягивается и завершает работу. Есть три способа справиться с этим:

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

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

from twisted.internet import reactor
from twisted.internet.task import deferLater
from twisted.internet.defer import inlineCallbacks, returnValue

@inlineCallbacks
def slowButSteady():
    result = SomeResult()
    for something in somethingElse:
        result.workHardForAMoment(something)
        yield deferLater(reactor, 0, lambda : None)
    returnValue(result)
9 голосов
/ 11 февраля 2011

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

Вот (очень простой) пример, использующий ваш пример выше:

def blocking1():
    while True:
        yield 'first blocking function example'

def blocking2():
    while True:
        yield 'second blocking function example'


tasks = [blocking1(), blocking2()]

# Repeat until all tasks have stopped
while tasks:
    # Iterate through all current tasks. Use
    # tasks[:] to copy the list because we
    # might mutate it.
    for t in tasks[:]:
        try:
            print t.next()
        except StopIteration:
            # If the generator stops, remove it from the task list
            tasks.remove(t)

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

2 голосов
/ 11 февраля 2011

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

1 голос
/ 11 февраля 2011

Если вы не хотите использовать полную многопоточность ОС, вы можете попробовать Stackless , который является вариантом Python, который добавляет много интересных функций, включая «микропотоки».Есть несколько хороших примеров , которые вы найдете полезными.

0 голосов
/ 10 ноября 2013

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

Если вы хотите «съесть» оба итератора один за другим, не пытайтесь заставить вашу программу полностью поглотить «blocking1 ()», прежде чем продолжить ...

for b1, b2 in zip(blocking1(), blocking2()):
    print 'this will be shown', b1, 'and this, too', b2
...