非同期HTTPクライアント、またはマルチスレッドが不要な理由

少し前に、 Python上のサイトのクライアントパーサーに関するメモがHabréにジャンプしました。 この例の著者は、マルチスレッドネットワークアプリケーションの問題を分析しました。



しかし、同じタスク(というよりは、その主要部分-httpサーバーとの並列接続)は、フローなしで効果的に解決できるように思えました。







はじめに、TwistedとTornadoについての記事を見て、頭をかき、ノンブロッキングソケットのドキュメントを掘り下げて、非同期httpクライアントサーバーをスケッチしました 。



以下に、アプリケーションの主要部分のソースコードと説明を示します。



インポートソケット

インポート選択

インポートシステム

インポートエラー

輸入時間



設定インポート から *



def ioloop ( ip_source 、 request_source ) :

"" "非同期ループ



ip_source-無限反復可能、接続用のIPアドレスを発行します。

request_source-反復可能で、リクエストボディを生成します;

» ""

starttime = time 。 時間( )



#ソケットプールを開きます。 接続本体の要求と応答を格納する辞書

epoll = select エポール( )

接続= { } ; 応答= { } ; リクエスト= { }

バイト送信= 0.0

バイト読み取り= 0.0

タイムアウト= 0.3



#最初のリクエストを選択

request = request_source 。 次へ( )

試してください :

Trueの場合 :

#最小数よりも少ない場合は、接続数を確認します

#可能ですが、まだリクエストがあります-もう1つ追加します。

#

connection_num = len ( connections )



connection_num < CLIENT_NUM および requestの場合:

ip = ip_source 。 次へ( )

print "%sへの接続を開きます。" % ip

clientsocket = socket 。 ソケット( socket。AF_INET 、

ソケット SOCK_STREAM )

#やや非自明。 ノンブロッキングソケットスロー

すぐに接続できない場合は#EINPROGRESSエラー例外。

#エラーを無視して、ソケット上のイベントの待機を開始します。

#

clientsocket 。 セットブロッキング( 0 )

試してください :

res = clientsocket 。 接続( ( ip 、 80 ) )

ソケットを除く 。 エラー、エラー:

#

if err errno ! = errno 。 アインプロレス:

上げる

#ソケットをプールと接続辞書に追加します

epoll 。 登録( clientsocket .fileno ( ) 、. EPOLLOUTを選択)

接続[ clientsocket 。 fileno ( ) ] = clientsocket

要求[ clientsocket 。 fileno ( ) ] =リクエスト

応答[ clientsocket 。 fileno ( ) ] = ""



#「プール」-つまり、イベントのコレクション

#

events = epoll 。 ポーリング(タイムアウト)

fileno 、イベントのイベント:

イベントの場合&を選択します。 EPOLLOUT :

#リクエストの一部を送信しています...

#

試してください :

byteswritten = connections [ fileno ] 。 送信(リクエスト[ fileno ] )

リクエスト[ fileno ] =リクエスト[ fileno ] [ byteswritten : ]

byteswritten 、 「送信バイト数」を出力します。

bytessent + = byteswritten

if len ( requests [ fileno ] ) = = 0 :

epoll 。 変更( fileno 、 .EPOLLIN を選択)

「読み取りに切り替えられました。」

ソケットを除く 。 エラー、エラー:

print "Socket write error:„ 、 err

Exception 、 err を除く :

「不明なソケットエラー:„ 、エラー

elifイベント&を選択します。 エポリン:

#回答の一部を読む...“

#

試してください :

バイト=接続[ fileno ] 。 recv ( 1024 )

ソケットを除く 。 エラー、エラー:

#「ピアによる接続リセット」エラーをキャッチ-

#多数の接続で発生

#

if err errno = = errno ECONNRESET :

epoll 。 登録解除( fileno )

接続[ fileno ] 。 閉じる( )

del connections [ fileno ]

印刷 »ピアによる接続のリセット。」

続ける

その他 :

エラーを上げる



print len ( bytes ) 、 「読み取られたバイト」。

bytesread + = len (バイト)

応答[ fileno ] + =バイト

バイトでない 場合 :

epoll 。 登録解除( fileno )

接続[ fileno ] 。 閉じる( ) ;

del connections [ fileno ]

「読み取り完了...終了」を 印刷 します。



#次のクエリを選択

リクエストの場合 :

request = request_source 。 次へ( )



「接続が残っている:„ 、 len ( connections )

len (接続)で ない 場合 :

破る

KeyboardInterrupt を除く :

「信号によって中断されたループ」を 印刷します 。

fdの場合 は、接続の靴下。 アイテム( ) :

靴下。 閉じる( )

epoll 。 閉じる( )



endtime = time 。 時間( )

timespent = endtime - starttime

応答を返す 、 timespent 、 bytesread 、 bytessent



ここでの教訓は単純です-ストリームをどこにでも押し込むべきではありません;さらに、マルチスレッドはプログラムの信頼性を低下させるだけで、テストで既知の問題を作成し、とらえどころのないバグの原因になる場合があります。 パフォーマンスは重要ではないが、何かを並列化したい場合は、通常のプロセスやプリミティブIPCでさえ正当化されることがよくあります。



さらに、Pythonには実際のカーネルレベルのスレッドはまだありませんが、いまいましいGILはまだ生きています。 したがって、マルチコアプロセッサではパフォーマンス上の利点は得られません。



もちろん、このスクリプトは不気味で急いで実行され、サーバーによる接続の中断やソケットへの読み取り/書き込み操作のエラーを処理せず、サーバーの応答を解析しませんが、cnn.comサイトのルートを何度もチャンネルの機能の限界-800-1000までドラッグしますKb / s :)



スクリプトソース全体はここのどこかにあります。



PSたぶん誰かがあなたが生産的に使えるものについて考えている

非同期クライアント? :)



All Articles