実際のStackless and Concurrence機能に進む前に、複数の同時接続を処理するネットワークアプリケーションを記述する最も簡単な方法を検討してください。
socket() bind() listen() accept() fork() -> read() write() ... close()
新しい着信接続ごとに、プロセスはfork()を介してコピーを作成します。 これは非常に高価な方法であり、さらにプロセス間の同期に問題があります。 単純なケースでは、親プロセスと子プロセスの間にパイプを作成し、データをシリアル化することで解決します。 より複雑なものには、プロセス間同期プリミティブが必要です。 プロセスの作成、破棄、および切り替えのコストについてさらに思い出してみましょう。 これらは、メモリと処理能力の両方で非常にリソースを消費する操作です。 したがって、多数の同時接続を処理することは非常に困難であろう。
ただし、このアプローチには1つの重要な利点があります-コードは非常に単純です。 アプリケーションのロジックは、対応する言語構造に直接転送されます-サイクルでネットワークを介してデータを受信する必要がある場合、それはサイクル演算子になります。 最初に1つのアクションを実行し、次に別のアクションを実行する必要がある場合、それはプログラム内の2つの連続したステートメントなどになります。
新しいプロセスを作成する代わりに、同じプロセス内に別のスレッドを作成すると、いくつかの問題が解消されます。スレッド間でデータを交換するのがはるかに簡単になります。 共通オブジェクトにメモリを割り当てるには、言語の通常の手段を使用するだけで十分です。スレッド間で共通オブジェクトへのリンクを転送しても安全であり、シリアル化でリソースを浪費しません。 これにより、多くのプロセッサリソースが節約されますが、明示的な同期と共有オブジェクトへのアクセスの必要性が排除されるわけではありません。 さらに、各オペレーティングシステムスレッドには独自のスタックがあり、数キロバイトのメモリを占有します。これに同時接続数を掛けると、数百メガバイトを占有する可能性があります。 しかし、メモリの損失を調整できる場合(安価)、スレッドの作成と破棄、コンテキストの切り替え、同期の計算コストは非常に顕著になります。 さらに、GILの呪いはPythonにかかっているため、マルチスレッドアプリケーションの有効性がさらに低下します。
生産性の向上におけるエンジニアリングの次のステップは、有限状態マシンの明示的な割り当てに基づくシングルスレッドアプリケーションでした。 各接続はオートマトンによって表され、各状態で入力データが何らかの方法で処理され、さらに状態が変化します。 マシンの状態は小さなデータ構造にすぎないため、オペレーティングシステムの個々のスレッドよりも、それらの作成、保存、および破壊がはるかに高速です。 そして、スレッドよりもはるかに少ないメモリを占有します。
有限状態マシンの明示的な割り当てに基づくアプリケーションでは、メインループは、イベントのすべての開いている接続の調査です。データが到着した、エラーが発生した、または送信バッファーのスペースが解放されました。 イベントごとに、ステートマシンでハンドラーが呼び出されます。 この調査を最適化する(そして何万もの接続をすばやくポーリングするのは簡単な作業ではありません)ために、最新のオペレーティングシステムはさまざまな非常に効果的ですが、他のインターフェイス(kqueue、epollなど)と互換性がありません ポータブルネットワークアプリケーションを作成するために、プログラマから接続ポーリングの実装の詳細を隠すlibeventなどの特別なライブラリが開発されました。
ただし、有限状態マシンを明示的に指定すると(各状態はプログラムの個別のセクションになります)、アプリケーションの構造は複雑で読みにくくなります。 実際、この場合の状態間の遷移はgotoステートメントの使用に似ています-状態が変化した場合、次の状態ハンドラーが配置されているソース全体で再度検索する必要があります。 ここでは単純なネットワークプロトコルを実装するサンプル・アプリケーション・フレームワークは、次のとおりです。
select() -> read_ready -> read(cmd) if state == "STATE1": if cmd == "CMD1": state = "STATE2" else: invalid_command() elif state == "STATE2": if cmd == "CMD2": state = "STATE1" else: invalid_command()
各ハンドラーがブロックされることはありません。これにより、非同期データ処理が実現されます。 このアーキテクチャで、アプリケーションがデータベースに要求を行う必要がある場合、要求を送信し、応答の待機状態に進み、制御をメインループに戻す必要があります。 データベースから応答が来ると、データを受信して処理するハンドラーが呼び出されます。 要求/応答スキームがマルチフェーズの場合(たとえば、SMTPで、接続を呼び出し、制御を与え、接続の確立を待機し、データの待機を開始し、サーバーからHELOを待ち、HELOを送信し、制御を与え、応答を待ち、応答を読むなど)、その後明示的な言明はプログラマの悪夢になります。
実装は複雑ですが、このアプローチには否定できない利点があります。接続ハンドラー間の同期は実質的に必要ありません。 実際、それらの間の切り替えは協調的に行われます-メインサイクルを明確に制御できる瞬間にのみ。 ハンドラーはどのような状況でも中断できません。つまり、グローバルオブジェクトにアクセスするためのすべての操作はアトミックであることが保証されます。 ミューテックス、セマフォ、その他のトラブルを忘れ、貴重なプロセッサクロックを消費していました。
スタックレス
ステートマシンのパフォーマンスと最初のソリューションのシンプルさを組み合わせる方法があります。 そのためには、Stackless Pythonが必要です。 Stackless Pythonは、Pythonインタープリターの改良版です。 プログラマーは、同期プリミティブのパフォーマンスを犠牲にすることなく、競合状態の問題なしに、マルチスレッドプログラミングを利用できます。 安価で軽量のStacklessマイクロフローを正しく使用すると、プログラムの構造を改善し、コードを読みやすくして、プログラマの生産性を向上させることができます。 仕組みを見てみましょう。
プログラマーの観点から見ると、タスクレット(スタックレス用語でマイクロフロー)を作成することは、新しいオペレーティングシステムスレッドを作成することと同じです:stackless.tasklet(your_func)(1、2、3)。 新しいタスクレットのコンテキストで関数your_func(1、2、3)の実行を開始します。 この関数の実行は、タスクレットが明示的にカーネルに制御を与えるまで(stackless.schedule())、またはブロックされて、情報の送受信を待機するまで継続されます。 たとえば、タスクレットはネットワークソケットからデータを受信したいが、まだ利用できません。 この時点で、タスクレットはI / O待機キューに入り、制御は次のタスクレットに順番に転送されます。 予想されるデータが到着すると、最初のタスクレットが制御を引き継ぎ、データの処理を続行します。
実際、同じロジックが有限状態マシンスキームで機能しました(協調マルチタスク、ソケットマネージャーを使用する必要性、および共通データ構造にアクセスするための同期プリミティブが必要ないこと)。主な違いは、タスクが通常の線形Pythonコードで記述されることです。 たとえば、ネットワークサービスへの呼び出しは次のように説明できます。
val = memcached.get("some-object-123") if val is None: res = list(mysql.query("select val from tbl where id=%d", 123)) if len(res): val = res[0] memcached.set("some-object-123", val)
各ネットワーク操作(memcached、データベースへのアクセス、HTTPリクエストの実行、SMTPを介した電子メールの送信など)は、結果が受信されるまでタスクレットを一時停止します。 待っている間、他のタスクレットを実行します。
タスクレットは、チャネルを使用して相互にデータを送信できます。 チャネルは、送信()と受信()の2つの主要なメソッドを持つオブジェクトです。 1つのタスクレットがch.send(some_object)データをチャネルに送信する場合、他のタスクレットはこのデータを受信できます:some_object = ch.receive()。 チャネルに保留中のタスクレットがない場合、送信者はデータが受信されるまでブロックされます。 また、チャネルに保留中のデータがない場合、受信タスクレットは表示されるまでブロックされます。 複数のタスクレットは1つのチャネルを使用でき、各チャネルはデータを送受信できます。 チャネルは、タスクレット間の主要な同期方法です。 たとえば、データベースへの永続的な接続の限られた数からプールを実装する場合、プールから接続を取得する操作は次のようになります。
def get(): if len(self._pool): return self._pool.pop(0) else: return self._wait_channel.receive()
プールに空き接続がある場合、そのうちの1つが使用されます。 そうでない場合、タスクレットはチャネルでブロックされ、誰かが接続を解放するまで待機します。 チャネルでブロックされたタスクレットは、コンピューター時間のビートを消費しません。 チャネルのロジックは、データがチャネルに配置されるとすぐに、タスクレットをスケジューラのキューに自動的に配置します。 プールに戻って運転化合物の敷地は次のようになります。
def put(conn): if self._wait_channel.balance < 0: self._wait_channel.send(conn) else: self._pool.append(conn)
「チャネルのバランス」がゼロより小さい場合、これは、一部のタスクレットがこのチャネルで待機していることを意味します。 この場合、プールに返された接続はチャネルに配置され、そこからタスクレットによってすぐに取り出され、最初にキューに入ってから実行が継続されます。
Stackless自体は、タスクレットコンテキストスイッチングシステム、スケジューラ、チャネルメカニズム、およびタスクレットのシリアル化であり、それらをディスクに保存し、ネットワークを介して転送し、中断された場所から実行を継続できます。 また、Greenletsパッケージもあります。これは、Stacklessの簡易バージョンです。 マイクロフロー(実際にはグリーンレット)のみを実装し、スケジューラーを含む残りのロジックはプログラマーが担当します。 このため、GreenletsはStacklessよりもわずかに(10-25%)遅くなりますが、特別なバージョンのインタープリターを必要としません。
並行性
実際のネットワークアプリケーションを作成するには、非ブロッキングソケットを操作するためのライブラリが必要です。これには、ネットワーク操作のタスクレットをブロックし、ネットワークイベントの発生時にタスクレットを実行し続けるソケットマネージャーが含まれます。 そのようなライブラリがいくつかあります:
単純な些細なこと 、
Eventlet (Greenletsのみ)、
gevent (Greenletsのみ)、および
Concurrence (GreenletsとStackless)。 私が伝えたいのは後者についてです。
並行性はlibeventに基づいており、そのメインループと接続バッファーシステムはCで実装されており、ネットワーク操作に優れたパフォーマンスを提供します。 ソケットマネージャー自体に加えて、Concurrenceはタイマーを作成し、スリープ(s)などの機能を使用し、多くの一般的なプロトコル(HTTPクライアント、HTTPサーバー(WSGI)、Memcached、MySQL-はい、本当の非同期MySQLクライアントライブラリを実装する機能を提供します、XMPP)。 上記の例(MemcachedおよびMySQLへの呼び出しを使用)は、特にConcurrenceで記述されています。 最小のWebサーバーを作成する方法は次のとおりです。
def hello_world(environ, start_response): start_response("200 OK", []) return ["<html>Hello, world!</html>"] def main(): server = WSGIServer(hello_world) server.serve(('localhost', 8000)) dispatch(main)
ディスパッチ関数は、メインの同時実行ループを開始し、メイン関数を実行する最初のタスクレットをキューに入れます。 次に、WSGIServerが起動し、接続を受け入れます。 接続ごとに個別のタスクレットが起動され、hello_world関数が実行されます。 後者は任意の複雑さで、非同期操作が含まれます。 システムがそれらの完了を待つ間、新しい接続は引き続き受け入れられます。
今、軟膏のハエ。 残念ながら、同時性は放棄され、サポートされなくなったようです。 作者は、パッチ付きのバグレポートを含む手紙に応答しません。 したがって、発見した修正されたバグ、および特に実装されたSMTPクライアントとThriftサポートを備えたWebDAVのHTTP PUTサポートを含むいくつかの追加機能を使用して、バージョンのConcurrenceを公開しました。 リポジトリは
githubにあります。
Stackless、Concurrence、またはその他のPython非同期プログラミングテクノロジーの使用を計画している人は、
ru-python-asyncメーリングリストに登録してください。
参照資料
スタックレス
Python-www.stackless.com成功事例
-www.stackless.com/wiki/Applications並行性
-opensource.hyves.org/concurrenceConcurrenceの私のバージョンは
github.com/JoyTeam/concurrenceです