重複の可能性:
Python での非同期 HTTP 呼び出し
複数の Web サービスから検索結果を取得し、結果をブレンドしてレンダリングする必要がある Django ビューがあります。Django でマルチスレッドを行ったことはありません。これを行う最新の効率的で安全な方法は何ですか?
私はまだそれについて何も知りませんが、geventは合理的な選択肢のようです。私はそれを使うべきですか?Django でうまく動作しますか? 私は他の場所を見るべきですか?
重複の可能性:
Python での非同期 HTTP 呼び出し
複数の Web サービスから検索結果を取得し、結果をブレンドしてレンダリングする必要がある Django ビューがあります。Django でマルチスレッドを行ったことはありません。これを行う最新の効率的で安全な方法は何ですか?
私はまだそれについて何も知りませんが、geventは合理的な選択肢のようです。私はそれを使うべきですか?Django でうまく動作しますか? 私は他の場所を見るべきですか?
geventについてはよくわかりません。最も簡単な方法は、threads[*]を使用することです。Pythonでスレッドを使用する方法の簡単な例を次に示します。
# std lib modules. "Batteries included" FTW.
import threading
import time
thread_result = -1
def ThreadWork():
global thread_result
thread_result = 1 + 1
time.sleep(5) # phew, I'm tiered after all that addition!
my_thread = threading.Thread(target=ThreadWork)
my_thread.start() # This will call ThreadWork in the background.
# In the mean time, you can do other stuff
y = 2 * 5 # Completely independent calculation.
my_thread.join() # Wait for the thread to finish doing it's thing.
# This should take about 5 seconds,
# due to time.sleep being called
print "thread_result * y =", thread_result * y
複数のスレッドを開始し、それぞれに異なるWebサービス呼び出しを行わせ、それらすべてのスレッドに参加させることができます。これらの参加呼び出しがすべて返されると、結果が表示され、それらをブレンドできるようになります。
より高度なヒント:タイムアウトを指定してjoinを呼び出す必要があります。そうしないと、アプリが応答を送信するのをユーザーが無期限に待機する可能性があります。さらに良いのは、リクエストがアプリに到着する前にこれらのWebサービス呼び出しを行うことです。それ以外の場合、アプリの応答性は、依存しているサービスに翻弄されます。
一般的なスレッドに関する注意:2つ(またはそれ以上)の異なるスレッドからアクセスできるデータには注意してください。同じデータへのアクセスは「同期」する必要があります。最も人気のある同期デバイスはロックですが、他にもたくさんあります。threading.Lockはロックを実装します。同期に注意しないと、アプリに「競合状態」が書き込まれる可能性があります。このようなバグは、確実に再現できないため、デバッグが難しいことで有名です。
私の簡単な例では、thread_resultはmy_threadとメインスレッドの間で共有されていました。my_threadが終了するまでメインスレッドはthread_resultにアクセスしなかったため、ロックは必要ありませんでした。my_thread.joinを呼び出さなかった場合、結果は20ではなく-10になることがあります。先に進んで自分で試してください。
[*]アイドル状態のコアがある場合でも、同時スレッドが同時に実行されないという意味で、Pythonには真のスレッドがありません。ただし、それでも同時実行は可能です。1つのスレッドがブロックされると、他のスレッドが実行できます。
3.2 で利用可能で、 2.xを含む以前のバージョンにバックポートされたfutures
を使用して、この問題をうまく解決しました。
私の場合、内部サービスから結果を取得して照合していました。
def _getInfo(request,key):
return urllib2.urlopen(
'http://{0[SERVER_NAME]}:{0[SERVER_PORT]}'.format(request.META) +
reverse('my.internal.view', args=(key,))
, timeout=30)
…
with futures.ThreadPoolExecutor(max_workers=os.sysconf('SC_NPROCESSORS_ONLN')) as executor:
futureCalls = dict([ (
key,executor.submit(getInfo,request,key)
) for key in myListOfItems ])
curInfo = futureCalls[key]
if curInfo.exception() is not None:
# "exception calling for info: {0}".format(curInfo.exception())"
else:
# Handle the result…
gevent は、タスクをより速く処理するのに役立ちません。リソースフットプリントに関しては、スレッドよりも効率的です。Django (通常は gunicorn 経由) で gevent を実行すると、Web アプリは通常の Django wsgi アプリよりも多くの同時接続を処理できるようになります。
しかし:これはあなたの問題とは何の関係もないと思います. あなたがやりたいことは、1 つの Django ビューで巨大なタスクを処理することですが、これは通常は良い考えではありません。個人的には、Django でスレッドや gevents greenlets を使用しないことをお勧めします。スタンドアロンの Python スクリプト、デーモン、またはその他のツールの要点はわかりますが、Web の場合はそうではありません。これにより、ほとんどの場合、不安定になり、リソース フットプリントが増加します。代わりに、dokkaebiと Andrew Gorcesterのコメントに同意します。ただし、どちらのコメントも多少異なります。これは、実際にはタスクの内容に依存するためです。
タスクを多数の小さなタスクに分割できる場合は、これらのサブタスクを処理する複数のビューを作成できます。これらのビューは JSON のようなものを返すことができ、フロントエンドから AJAX を介して使用できます。このように、ページのコンテンツが「入ってくる」ときに作成でき、ユーザーはページ全体が読み込まれるまで待つ必要がありません。
タスクが 1 つの巨大なチャンクである場合は、タスク キュー ハンドラーを使用することをお勧めします。セロリが思い浮かびます。Celeryが過剰すぎる場合は、zeroMQを使用できます。これは基本的に、上記の Andrew の説明と同じように機能します。処理するタスクをスケジュールし、タスクが完了するまで (通常は AJAX 経由でも) フロントエンド ページからバックエンドをポーリングします。ここでロングポーリングのようなものを使用することもできます。