よくある質問

time.sleep() を使ったこの例はなぜ並列に実行されないのですか?

Tornado の並行処理に初めて触れる多くの人は、次のようなコードを書きます。

class BadExampleHandler(RequestHandler):
    def get(self):
        for i in range(5):
            print(i)
            time.sleep(1)

このハンドラを同時に 2 回呼び出すと、最初の 5 秒のカウントダウンが完全に終了するまで、2 番目のカウントダウンが開始されないことがわかります。これは、time.sleep が**ブロッキング**関数であるためです。 IOLoop に制御を戻して他のハンドラを実行することができません。

もちろん、time.sleep はこれらの例では単なるプレースホルダーです。重要なのは、ハンドラの処理が遅くなった場合に何が起こるかを示すことです。実際のコードが何をしているかに関係なく、並行処理を実現するには、ブロッキングコードをノンブロッキングの同等のコードに置き換える必要があります。これは、次の 3 つのいずれかを意味します。

  1. コルーチン対応の同等の関数を見つける。 time.sleep の場合は、tornado.gen.sleep (または asyncio.sleep) を使用します。

    class CoroutineSleepHandler(RequestHandler):
        async def get(self):
            for i in range(5):
                print(i)
                await gen.sleep(1)
    

    このオプションが利用可能な場合は、通常、最良の方法です。役立つ可能性のある非同期ライブラリのリンクについては、Tornado wiki を参照してください。

  2. コールバックベースの同等の関数を見つける。 最初のオプションと同様に、コールバックベースのライブラリは多くのタスクで利用できますが、コルーチン用に設計されたライブラリよりも使用が少し複雑です。コールバックベースの関数を Future に変換します。

    class CoroutineTimeoutHandler(RequestHandler):
        async def get(self):
            io_loop = IOLoop.current()
            for i in range(5):
                print(i)
                f = tornado.concurrent.Future()
                do_something_with_callback(f.set_result)
                result = await f
    

    ここでも、適切なライブラリを見つけるために Tornado wiki が役立ちます。

  3. ブロッキングコードを別のスレッドで実行する。 非同期ライブラリが利用できない場合は、concurrent.futures.ThreadPoolExecutor を使用して、ブロッキングコードを別のスレッドで実行できます。これは、非同期対応の有無にかかわらず、あらゆるブロッキング関数に使用できる汎用的なソリューションです。

    class ThreadPoolHandler(RequestHandler):
        async def get(self):
            for i in range(5):
                print(i)
                await IOLoop.current().run_in_executor(None, time.sleep, 1)
    

ブロッキング関数と非同期関数の詳細については、Tornado ユーザーガイドの 非同期 I/O の章を参照してください。

私のコードは非同期です。なぜ 2 つのブラウザタブで並列に実行されないのですか?

ハンドラが非同期でノンブロッキングであっても、これを検証するのは驚くほど難しい場合があります。ブラウザは、同じページを 2 つの異なるタブで読み込もうとしていることを認識し、最初のタブが完了するまで 2 番目のリクエストを遅延させます。これを回避し、サーバーが実際に並列に動作していることを確認するには、次のいずれかの操作を行います。

  • URL に何かを追加して一意にする。両方のタブで http://localhost:8888 の代わりに、一方のタブで http://localhost:8888/?x=1 を、もう一方のタブで http://localhost:8888/?x=2 をロードします。

  • 2 つの異なるブラウザを使用する。たとえば、Firefox は、Chrome タブで同じ URL が読み込まれている場合でも、その URL を読み込むことができます。