2013-02-12 1 views
4

Я читал GEvent учебник и увидел этот интересный фрагмент:переключение контекста с «выходом»

import gevent 

def foo(): 
    print('Running in foo') 
    gevent.sleep(0) 
    print('Explicit context switch to foo again') 

def bar(): 
    print('Explicit context to bar') 
    gevent.sleep(0) 
    print('Implicit context switch back to bar') 

gevent.joinall([ 
    gevent.spawn(foo), 
    gevent.spawn(bar), 
]) 

В котором поток выполнения идет как этот обув -> Бар -> Foo -> бар. Невозможно сделать то же самое без модуля gevent, но с операциями yield? Я пытался сделать это с «урожаем», но по какой-то причине я не могу заставить его работать ... :(

ответ

5

Генераторы, используемые для этой цели, часто называются Задачи (среди многих других терминов), и я буду использовать этот термин здесь для ясности. Да, это возможно. На самом деле существует несколько подходов, которые работают и имеют смысл в некоторых контекстах. Тем не менее, ни один (что я знаю) работает без эквивалента по крайней мере для одного из gevent.spawn и gevent.joinall. Для более мощных и хорошо продуманных требуется эквивалент для обоих.

Основная проблема заключается в следующем: генераторы могут быть приостановлены (когда они нанесли yield), но это все. Чтобы снова выключить их, вам понадобится другой код, вызывающий next(). На самом деле вам даже нужно позвонить next() только что созданному генератору, чтобы он сделал что-нибудь для начала. Аналогично, сам генератор - не лучшее место, чтобы решить, что следует делать дальше. Итак, вам нужен цикл, который инициирует срез времени каждой задачи (запускает их до следующего yield) и переключается между ними неограниченное время. Это обычно называют планировщиком. Они, как правило, становятся действительно волосатыми очень быстро, поэтому я не буду пытаться писать полный планировщик в один ответ. Есть, однако, некоторые основные понятия, я могу попытаться объяснить:

  • Один обычно лечит yield как давать управление обратно Sheduler (действует аналогично gevent.sleep(0) в коде). Это означает, что генератор делает все, что он хочет сделать, и когда он находится в месте, где контекстный переключатель удобен и, возможно, полезен, он yield.
  • В Python 3.3+, yield from - очень полезный инструмент для делегирования другому генератору. Если вы не можете использовать его, вы должны заставить планировщик эмулировать стек вызовов и вернуть значения маршрута в нужное место и сделать что-то вроде result = yield subtasks() в ваших задачах. Это медленнее, сложнее реализовать и вряд ли даст полезные трассировки стека (yield from делает это бесплатно). Но до недавнего времени это было лучшее, что у нас было.
  • В зависимости от вашего варианта использования вам может потребоваться широкий спектр инструментов для управления задачами. В обычных примерах возникают новые задачи, ожидающие завершения задачи, ожидающие завершения любой из нескольких задач, обнаружение сбоя (неперехваченное исключение) других задач и т. Д. Обычно они обрабатываются планировщиком, а задачам предоставляется API для связи с планировщиком. Оптимальным способом (но не всегда идеальным) для этого сообщения является yield ing специальные значения.
  • Одно довольно важное различие между задачами генератора и gevent (и аналогичными библиотеками) заключается в том, что контекстные переключатели в последнем неявны, в то время как задачи делают тривиальным определение контекстных переключателей: только то, что yield [from] может запускать код планировщика.Например, вы можете убедиться, что кусок кода является атомарным (w.r.t. другие задачи, если вы добавляете потоки в микс, вам приходится беспокоиться о них самостоятельно), просто просматривая код, не проверяя что-либо, которое он вызывает.

Наконец-то, возможно, вас заинтересует tutorial от Greg Ewing по созданию такого планировщика. (Это произошло на python-ideas, в то время как мозговой штурм над тем, что сейчас является PEP 3156. Эти почтовые потоки также могут представлять интерес для вас, хотя веб-архив не подходит для чтения сотен писем в десятках потоков, написанных полгода назад .)

+0

Очень интересно. Множество вещей, о которых я не знал. Этот учебник тоже кажется приятным. ура – kaiseroskilo

2

Ключ должен осознать, что вам нужно будет предоставить свою собственную вождение, . Я представил простой демо ниже я поленился и использовал объект очереди, чтобы обеспечить FIFO, я не использовал питона для значимого проекта на некоторое время

#!/usr/bin/python 

import Queue 

def foo(): 
    print('Constructing foo') 
    yield 
    print('Running in foo') 
    yield 
    print('Explicit context switch to foo again') 

def bar(): 
    print('Constructing bar') 
    yield 
    print('Explicit context to bar') 
    yield 
    print('Implicit context switch back to bar') 

def trampoline(taskq): 
    while not taskq.empty(): 
     task = taskq.get() 
     try: 
      task.next() 
      taskq.put(task) 
     except StopIteration: 
      pass 

tasks = Queue.Queue() 
tasks.put(foo()) 
tasks.put(bar()) 

trampoline(tasks) 

print('Finished') 

и при запуске:.

$ ./coroutines.py 
Constructing foo 
Constructing bar 
Running in foo 
Explicit context to bar 
Explicit context switch to foo again 
Implicit context switch back to bar 
Finished 
+0

Делает смысл. Спасибо за демо. Мне было интересно, сможет ли генераторА получить следующий (генератор Б) (и генератор Б, чтобы получить следующий (генератор А) и т. Д.). – kaiseroskilo

+2

Проблема в том, что это три раза: во-первых, я ничего не знаю о возможности генерации генератора, что необходимо для настройки взаимной рекурсии. Во-вторых, вызов следующего/0 должен быть возвращен до того, как будет обработан оператор yield, что приведет к попытке повторно ввести генератор, который еще не уступил. В-третьих, это взаимная рекурсия на языке без исключения вызова хвоста, поэтому вы, в конце концов, исчерпаете стек, если генераторы дают слишком много раз. – Recurse

 Смежные вопросы

  • Нет связанных вопросов^_^