広範で一般的なもの(つまり、ネットや他のプラットフォームに固有のものではない)では、サービスはプログラムされています2つの方法のいずれか、または両方の組み合わせで
接続ごとのスレッド:注意しておきますが、これはカーネルスレッドやプロセスなどの単一のスケジューリングエンティティ、あるいはユーザー空間でプラットフォームによって実装された軽いスレッドまたはコルーチンを使用します。単一の要求を処理することに順調に関わっています。
例の擬似コード:上記のサンプルの
function run(connection)
while(connection is open)
frobnicate(connection)
function main
listener = new Listener(port)
threads = new Collection<Thread>()
while(running)
connection = listener.accept()
threads.add(new Thread(run, connection))
join(threads)
exit()
重要な機能:
- これは、あなたがそれを置くとおりに、接続モデルごとのスレッドに従います。スレッドで の接続が終了すると、接続は終了します。
- このプログラムを現実世界で実行する場合は、 が数百スレッド以上を処理する必要があります。ほとんどのプラットフォームは、このレベルの約 で解約を開始します。いくつかは、同時接続数十万台の の中で、さらに多くのスレッドを処理するように特別に設計されています。
- 接続はマルチスレッドで処理されますが、実際には着信接続を受け入れる はメインスレッドでのみ処理されますが、悪意のあるホストが接続しようとしたが故意に失敗した場合はDoSのポイントになる可能性があります。
- 実行を停止する時間があることをプログラムが検出すると、メインスレッドは終了時に終了しないように、他の スレッドに参加します。再び、つまり、プログラムは、すべての古いスレッドがデータの処理を終えるまで終了しないことを意味します。
代わりに、結合を使用しないことがあります。この場合、他のスレッドは が無計画に終了します。
ノンブロッキング:1つの実行スレッドが複数の接続を管理し、特定の接続がストールしたとき、データがネットワークを介して移動するのを待っているとき、またはディスクから読み込むときにプロセスがスキップするそれ以上の接続で動作します。
例の擬似コード:このスニペットの
function main
connections = new Collection<Connection>()
listener = new Listener()
connections.append(listener)
foreach connection in connections:
if connection.ready():
if connection is listener:
connections.add(connection.accept())
else:
if connection is open:
nb_frobnicate(connection)
else:
connections.remove(connection)
yield()
if(not running)
exit()
特徴:マルチスレッド・バージョンで
- は、各スレッドが単一の接続を処理しました。 その接続が使用する準備ができていない場合、スレッドはブロックし、別のスレッドは の作業を実行できます。この実装で
frobnicate
がブロックされていると、災害になります。たとえ1つの接続が準備できなくなっても、他の接続は作業を行うことができます。 これに対処するには、接続で非ブロック操作を使用するように注意している代替機能が使用されます。これらの読み取りと書き込みはすぐに戻ります。 呼び出し側にどれくらいの作業を実行できるかを示す値が返されます。その場合、 がまったく動作しない場合、プログラムは接続がより多くの作業の準備ができていることを知っているので、ただちに としてもう一度試す必要があります。私は nb_frobnicate
に名前を変更して、この機能がノンブロッキングであることを注意していることを示しています。
- ほとんどのプラットフォームは、接続上でループを抽象化できる機能、すなわち を提供し、それぞれにデータがあるかどうかを確認します。これは、
select()
または poll()
のいずれかと呼ばれます(どちらも利用可能です)。それでも、私は 以上のネットワーク接続で作業したいかもしれないので、この方法で表示することにしました。私が知る限り、 は、すべての可能なブロック操作を総称的に待つことはできません。 ディスクIO(Windowsのみ)、タイマー、ハードウェア割り込み(画面の更新など)を待つ必要がある場合は する必要がありますさまざまなイベントソースを手動でポーリングします。
- は、
yield()
関数です。この関数は、IO割り込み が発生するまでスリープします。これは、IOが処理される準備ができていない場合に必要です。これを行わなかった場合、 プログラムはビジー待機し、各ポートをチェックして再確認し、 にはデータが準備されていないことがわかり、不必要にCPUを浪費します。プログラムが ネットワークIO(またはPOSIXシステム上のディスク)で待機する必要がある場合は、 ブロッキングモードでselect/poll
を呼び出すことができ、ネットワーク接続が可能になるまでスリープします。しかし、もっと多くの種類のイベントを待つ場合は のノンブロッキングバージョンを使用し、他のイベントソースをそのイディオムに従ってポーリングしてから、 がすべてブロックするようにしなければなりません。
- 上記の例のスレッド結合には、ここでは対応しません。代わりに、非結合動作に似た です。 ポーリングコレクションからリスナーを削除してから同様の動作をしてから、一度exitを呼び出してください。 コレクションが空です。
- プログラム全体は、単純なループと関数呼び出しで構成されています。 は、コンテキストスイッチのコストを招くことはありません。これの欠点は、 プログラムが厳密に順次であり、 が存在する場合、複数のコアを利用できないことです。ほとんどのサービスはIO になり、接続が準備される速度によって制限され、 架空の
frobnicate()
機能のコストではないため、これはほとんど問題ではありません。
前述したように、これらを組み合わせて使用することで、マルチコアシステムでのCPU使用率の向上などの複数のスケジューリングエンティティの利点の多く、コンテクストの切り替えが少なくなるため負荷が軽減されます。この
例:このプログラムに関する
function worker(queue)
connections = Collection<Connection>()
while(running)
while(queue not empty)
connections.add(queue.pop)
foreach connection in select(connections)
if connection is open:
nb_frobnicate(connection)
else:
connections.remove(connection)
function main
pool = new Collection<Thread, Queue<Connection> >()
for index in num_cpus:
pool[index] = new Thread(worker), new Queue<Connection>
listener = new Listener(port)
while(running)
connection = listener.accept()
selected_thread = 0
for index in pool.length:
if pool[index].load() < pool[selected_thread].load()
selected_thread = index
pool[selected_thread].push(connection)
pool[selected_thread].wake()
ノート:
- これは、シングルスレッドバージョンのインスタンスの束を作成しますが、彼らは マルチスレッドなので、彼らはと通信する必要がありますどういうわけか、お互いに。これは各スレッドに対して
Queue<Connection>
を使用して行われます。
- また、シングルスレッドプログラムと同じ理由で ハンドラを使用していることに注意してください
- ワーカースレッドのプールは、所有しているプロセッサの数によって制限されています。
おそらく を一度に実行するよりも多くのスレッドを待機させることの利点がほとんどないためです。実際には、使用する最適なスレッド数は アプリケーションごとに異なる場合がありますが、CPUの数は十分な見込みが多いことがよくあります。
- 以前のように、メインスレッドは接続を受け入れ、それらをワーカースレッドに渡します。 スレッドが既に他のスレッドで作業を完了し、準備が整うのを待っている場合は、 新しい接続がすぐに準備されていても、スレッドはそこに座ります。これを緩和するために、 メインスレッドはワーキングによってワーカースレッドに通知し、ブロッキング 操作を戻します。これが通常の読み取りだった場合は、おそらく というコードが返されますが、
select()
を使用しているため、準備完了の空のリストが返され、スレッドはもう一度そのキューを空にできます。
編集:追加のサンプルコード:
あなたは何を達成しようとしていますか? 「クライアントごとのスレッド」のどの側面を避けたいですか? –