5

私は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->bar->foo->barのようになります。geventモジュールなしで、yieldステートメントを使用して同じことを行うことはできませんか?私は「yield」でこれをやろうとしていましたが、何らかの理由でそれを機能させることができません... :(

4

2 に答える 2

5

この目的で使用されるジェネレーターは、多くの場合 (他の多くの用語の中でも)タスクと呼ばれます。ここでは、わかりやすくするためにその用語を使用します。はい、可能です。実際、状況によっては機能し、理にかなっているアプローチがいくつかあります。ただし、(私が認識している) は、 と の少なくとも 1 つに相当するものなしでは機能しませgevent.spawngevent.joinall。より強力で適切に設計されたものには、両方に相当するものが必要です。

基本的な問題は次のとおりです。ジェネレーターは ( にヒットしたときに) 中断される可能性がありますyieldが、それだけです。それらを再び開始するには、next()それらを呼び出す他のコードが必要です。実際、最初から何かnext()を行うには、新しく作成されたジェネレーターを呼び出す必要さえあります。同様に、ジェネレーター自体は、次に何を実行するかを決定するのに最適な場所ではありません。したがって、各タスクのタイム スライスを開始し (それらを次の に実行し)、それらを無期限に切り替えるループが必要です。これは通常、スケジューラと呼ばれます。それらは非常に急速に毛むくじゃらになる傾向があるため、1 つの回答で完全なスケジューラーを記述しようとはしません。ただし、説明しようとすることができるいくつかのコアコンセプトがあります。yield

  • 通常、yield制御をシェデュラーに戻すものとして扱います (実際にgevent.sleep(0)は、コード内と同様です)。つまり、ジェネレーターはやりたいことを何でも実行し、コンテキスト スイッチが便利でおそらく役立つ場所にある場合は、それを実行しyieldます。
  • Python 3.3+ では、yield from別のジェネレーターにデリゲートするための非常に便利なツールです。使用できない場合は、スケジューラーにコール スタックをエミュレートさせ、戻り値を適切な場所にルーティングresult = yield subtasks()し、タスクのようなことを行う必要があります。これは遅く、実装がより複雑で、有用なスタック トレースを生成する可能性は低いです (yield fromこれは無料で行います)。しかし、最近まで、それは私たちが持っていた最高のものでした.
  • ユースケースによっては、タスクを管理するためにさまざまなツールが必要になる場合があります。一般的な例としては、より多くのタスクを生成する、タスクが完了するのを待つ、いくつかのタスクのいずれかが完了するのを待つ、他のタスクの失敗 (キャッチされていない例外) を検出するなどがあります。これらは通常、スケジューラによって処理され、タスクには API が与えられます。スケジューラと通信します。このコミュニケーションを行うためのきちんとした (常に完璧であるとは限りませんが) 方法は、yield特別な価値観を伝えることです。
  • ジェネレータベースのタスクと gevent (および同様のライブラリ) のかなり重要な違いの 1 つは、後者のコンテキスト スイッチは暗黙的であるのに対し、タスクではコンテキスト スイッチを簡単に識別できることです:yield [from]スケジューラ コードを実行できる可能性のあるもののみ。たとえば、コードが呼び出しているものを調べることなく、コードを見るだけで、コードの一部がアトミックかどうかを確認できます (他のタスクに関して。スレッドをミックスに追加する場合は、それらについて個別に心配する必要があります)。

最後に、このようなスケジューラーの作成に関するGreg Ewing のチュートリアルに興味があるかもしれません。(これは、現在の PEP 3156 についてブレインストーミングを行っているときに思いつきましたpython-ideas。これらのメール スレッドも興味があるかもしれませんが、Web ベースのアーカイブは、半年前に書かれた数十のスレッドで数百のメールを読むのにはあまり適していません。 )

于 2013-02-12T21:28:03.913 に答える
2

重要なのは、独自の駆動ループを提供する必要があることを理解することです。以下に簡単なデモを用意しました。私は怠け者で、Queue オブジェクトを使用して FIFO を提供していました。しばらくの間、重要なプロジェクトで Python を使用していませんでした。

#!/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
于 2013-02-12T21:09:58.147 に答える