ツイストアプリケーションのマルチプロセス操作をサポートする方法はいくつかあります。ただし、最初に答えるべき重要な質問の1つは、並行性モデルがどのようなものであると期待するか、およびアプリケーションが共有状態をどのように処理するかです。
単一プロセスのTwistedアプリケーションでは、同時実行性はすべて協調的であり(Twistedの非同期I / O APIの助けを借りて)、Pythonオブジェクトが移動する場所ならどこでも共有状態を維持できます。アプリケーションコードは、制御を放棄するまで、他に何も実行されないことを認識して実行されます。さらに、共有状態の一部にアクセスしたいアプリケーションのどの部分も、おそらく非常に簡単にアクセスできます。これは、その状態が、アクセスしやすい退屈な古いPythonオブジェクトに保持されているためです。
複数のプロセスがある場合、それらがすべてツイストベースのアプリケーションを実行している場合でも、2つの形式の同時実行性があります。1つは前のケースと同じです。特定のプロセス内では、並行性は協調的です。ただし、複数のプロセスが実行されている新しい種類があります。プラットフォームのプロセススケジューラは、これらのプロセス間でいつでも実行を切り替える可能性があり、これを制御することはほとんどできません(また、いつ発生するかについての可視性もほとんどありません)。2つのプロセスを異なるコアで同時に実行するようにスケジュールすることもできます(これはおそらくあなたが望んでいることです)。これは、1つのプロセスが、2番目のプロセスがいつやって来て、ある共有状態で動作しようとするかを知らないため、一貫性に関する保証が失われることを意味します。
単一プロセスモデルとは異なり、すべてのコードが到達できる状態を保存するための便利で簡単にアクセスできる場所はもうありません。1つのプロセスに入れると、そのプロセス内のすべてのコードは通常のPythonオブジェクトとして簡単にアクセスできますが、他のプロセスで実行されているコードは簡単にアクセスできなくなります。プロセスが相互に通信できるようにするために、RPCシステムを見つける必要がある場合があります。または、各プロセスがそのプロセスに格納された状態を必要とする要求のみを受信するように、プロセス分割を設計することもできます。この例としては、セッションのあるWebサイトがあります。このサイトでは、ユーザーに関するすべての状態がセッションに保存され、セッションはCookieによって識別されます。フロントエンドプロセスは、Webリクエストを受信し、Cookieを検査し、どのバックエンドプロセスがそのセッションを担当しているかを調べます。次に、リクエストをそのバックエンドプロセスに転送します。このスキームは、バックエンドが通常通信する必要がないことを意味します(Webアプリケーションが十分に単純である限り、つまり、ユーザーが相互に対話したり、共有データを操作したりしない限り)。
その例では、事前分岐モデルは適切ではないことに注意してください。フロントエンドプロセスは、バックエンドプロセスによって処理される前にすべての着信要求を検査できるように、リスニングポートを排他的に所有する必要があります。
もちろん、アプリケーションには多くの種類があり、状態を管理するための他の多くのモデルがあります。マルチプロセッシングに適したモデルを選択するには、まず、アプリケーションにとってどのような種類の同時実行が意味をなすのか、およびアプリケーションの状態をどのように管理できるのかを理解する必要があります。
そうは言っても、Twistedの非常に新しいバージョン(現時点ではリリースされていません)を使用すると、リスニングTCPポートを複数のプロセス間で共有するのは非常に簡単です。これを実現するためにいくつかの新しいAPIを使用する1つの方法を示すコードスニペットを次に示します。
from os import environ
from sys import argv, executable
from socket import AF_INET
from twisted.internet import reactor
from twisted.web.server import Site
from twisted.web.static import File
def main(fd=None):
root = File("/var/www")
factory = Site(root)
if fd is None:
# Create a new listening port and several other processes to help out.
port = reactor.listenTCP(8080, factory)
for i in range(3):
reactor.spawnProcess(
None, executable, [executable, __file__, str(port.fileno())],
childFDs={0: 0, 1: 1, 2: 2, port.fileno(): port.fileno()},
env=environ)
else:
# Another process created the port, just start listening on it.
port = reactor.adoptStreamPort(fd, AF_INET, factory)
reactor.run()
if __name__ == '__main__':
if len(argv) == 1:
main()
else:
main(int(argv[1]))
fork
古いバージョンでは、ポートの共有に使用するのをやめることができる場合があります。ただし、これはエラーが発生しやすく、一部のプラットフォームでは失敗し、Twistedを使用するためのサポートされている方法ではありません。
from os import fork
from twisted.internet import reactor
from twisted.web.server import Site
from twisted.web.static import File
def main():
root = File("/var/www")
factory = Site(root)
# Create a new listening port
port = reactor.listenTCP(8080, factory)
# Create a few more processes to also service that port
for i in range(3):
if fork() == 0:
# Proceed immediately onward in the children.
# The parent will continue the for loop.
break
reactor.run()
if __name__ == '__main__':
main()
これは、新しく作成されたプロセス(子)が元のプロセス(親)からすべてのメモリとファイル記述子を継承するforkの通常の動作のために機能します。プロセスは他の方法では分離されているため、少なくとも実行中のPythonコードが実行される限り、2つのプロセスは互いに干渉しません。ファイル記述子は継承されるため、親または子のいずれかがポートで接続を受け入れることができます。
HTTPリクエストの転送は非常に簡単な作業であるため、これらの手法のいずれかを使用すると、パフォーマンスが大幅に向上することに気付くとは思えません。前者は、デプロイメントを簡素化し、非HTTPアプリケーションでより簡単に機能するため、プロキシよりも少し優れています。後者はおそらく、受け入れる価値があるよりも責任が大きいでしょう。