28

着信トラフィックの TCP ソケットをリッスンし、受信したデータをメイン スレッドがアクセスできるようにバッファリングするワーカー スレッドがあります (このソケットをAと呼びましょう)。ただし、ワーカー スレッドは、データが入ってこない場合でも、いくつかの定期的な操作 (たとえば、1 秒に 1 回)select()を実行する必要があります。(ノンブロッキング ソケットで呼び出しreceive()てから 1 秒間スリープするのは良くないことに注意してください。メイン スレッドが常にすぐに処理できるとは限らない場合でも、着信データはメイン スレッドですぐに利用できるようにする必要があります。バッファリングが必要です。)

ここで、ワーカー スレッドに他の処理をすぐに実行するよう通知できる必要もあります。select()メイン スレッドから、すぐにワーカー スレッドを返す必要があります。今のところ、私はこれを次のように解決しました(基本的にここここから採用されたアプローチ):

プログラムの起動時に、ワーカー スレッドはこの目的のためにデータグラム (UDP) タイプの追加のソケットを作成し、それを任意のポートにバインドします (このソケットをBと呼びましょう)。同様に、メイン スレッドは送信用のデータグラム ソケットを作成します。への呼び出しselect()で、ワーカー スレッドはABの両方を にリストしますfd_set。メインスレッドがシグナルを送る必要があるとき、それsendto()は の対応するポートへの数バイトlocalhostです。ワーカー スレッドに戻り、 Bfd_setがafterselect()リターンに残っている場合は、recvfrom()が呼び出され、受信したバイトは単純に無視されます。

これは非常にうまく機能しているようですが、主にBに追加のポートをバインドする必要があるため、また、失敗する可能性のあるソケット API 呼び出しがいくつか追加されるため、このソリューションが気に入っているとは言えません。ケースごとに適切なアクションを見つけたいと本当に思っています。

理想的には、 Aselect()を入力として取り、すぐに返す以外は何もしない関数を呼び出したいと思います。しかし、私はそのような機能を知りません。(たとえば、ソケットは可能だと思いshutdown()ますが、副作用は実際には受け入れられません:)

これが不可能な場合、次善の策はBを作成することです。これは、実際の UDP ソケットよりもはるかにダミーであり、限られたリソース (合理的な量のメモリを超えて) を実際に割り当てる必要はありません。Unix ドメインソケットはまさにこれを行うと思いますが、解決策は、現在私が持っているものよりもはるかにクロスプラットフォームである必要はありませんが、適度な量の#ifdefものは問題ありません. (私は主に Windows と Linux をターゲットにしています - ちなみに C++ を書いています。)

2 つの別個のスレッドを取り除くためにリファクタリングを提案しないでください。この設計が必要なのは、メイン スレッドが長期間にわたってブロックされる可能性があり (たとえば、集中的な計算を実行していて、receive()計算の最も内側のループから定期的に呼び出しを開始できない場合など)、その間、誰かが着信データをバッファリングする必要があるためです。 (そして、私が制御できない理由により、送信者になることはできません)。

これを書いている今、誰かが必ず「Boost.Asio」と答えるだろうと思ったので、初めて見ました... 明らかな解決策を見つけることができませんでした。ソケットA の作成方法にも (簡単に) 影響を与えることはできませんが、必要に応じて他のオブジェクトにラップさせることができるはずです。

4

4 に答える 4

36

あなたはほとんどそこにいます。「セルフパイプ」トリックを使用します。パイプを開き、それをselect()read and writeに追加しfd_set、メイン スレッドから書き込み、ワーカー スレッドのブロックを解除します。POSIX システム間で移植可能です。

あるシステムで Windows 用の同様の手法の変形を見たことがあります (実際には、上記の方法と一緒に使用され、 で区切られてい#ifdef WIN32ます)。ブロック解除は、ダミーの (バインドされていない) データグラム ソケットを追加してfd_setから閉じることで実現できます。欠点は、もちろん、毎回開き直さなければならないことです。

ただし、前述のシステムでは、これらの方法は両方とも控えめに使用され、予期しないイベント (たとえば、シグナル、終了要求) に対して使用されます。select()推奨される方法は、ワーカー スレッドに対して何かがスケジュールされるまでの時間に応じて、依然として可変のタイムアウトです。

于 2008-12-21T12:26:38.957 に答える
3

ソケットではなくパイプを使用すると、別のプロセスがパイプを取得して混乱させる可能性がないため、少しクリーンになります。

UDP ソケットを使用すると、浮遊パケットが入ってきて干渉する可能性が確実に生じます。

匿名パイプは、他のプロセスでは使用できません (それを与えない限り)。

シグナルを使用することもできますが、マルチスレッド プログラムでは、必要なスレッドを除くすべてのスレッドでそのシグナルがマスクされていることを確認する必要があります。

于 2008-12-22T07:47:00.627 に答える
1

UNIXでは、パイプを使用するのは簡単です。Windowsを使用していて、コードをunixと互換性を保つためにselectステートメントを使い続けたい場合は、バインドされていないUDPソケットを作成して閉じるというトリックがうまく機能します。ただし、マルチスレッドセーフにする必要があります。

このマルチスレッドセーフを作成するために私が見つけた唯一の方法は、selectステートメントが実行されているのと同じスレッドでソケットを閉じて再作成することです。もちろん、スレッドが選択でブロックしている場合、これは困難です。そして、WindowsでQueueUserAPCを呼び出します。selectステートメントでWindowsがブロックしている場合、スレッドは非同期プロシージャコールを処理できます。QueueUserAPCを使用して、別のスレッドからこれをスケジュールできます。Windowsはselectを中断し、同じスレッドで関数を実行して、selectステートメントを続行します。これで、APCメソッドでソケットを閉じて再作成できます。スレッドセーフが保証されており、信号を失うことはありません。

于 2012-12-24T13:18:38.873 に答える