よくある質問¶
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 つのいずれかを意味します。
コルーチン対応の同等の関数を見つける。
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 を参照してください。
コールバックベースの同等の関数を見つける。 最初のオプションと同様に、コールバックベースのライブラリは多くのタスクで利用できますが、コルーチン用に設計されたライブラリよりも使用が少し複雑です。コールバックベースの関数を 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 が役立ちます。
ブロッキングコードを別のスレッドで実行する。 非同期ライブラリが利用できない場合は、
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 を読み込むことができます。