Unices システムで非同期 I/O アラートに使用できる機能 (Linux の epoll、BSD システムの kqueue、Solaris の /dev/poll または I/O ポートなど) はすべて、ユーザーがポインタを指定して、ユーザーが I/O アラートを受け取りたいファイル記述子。
通常、このポインターでは、ユーザーはファイル記述子を抽象化する構造 (「ストリーム」構造など) へのポインターを指定し、ユーザーは新しいファイル記述子が開かれるたびに新しい構造を割り当てます。
例えばstruct stream { int fd; int flags; callback_t on_read_fn; /* ... */ };
さて、私の質問は次のとおりです。ユーザーがマルチスレッド環境で割り当てたこの構造を安全に解放するにはどうすればよいですか?
epoll/kqueue/etc の性質上、これについて質問します。通常、カーネルからイベントのベクトルを「ダウンロード」するスレッドがあり、I/O の準備ができているファイル記述子と、それに関連付けられたユーザー ポインターが含まれています。ファイル記述子。
ここで、2 つのスレッドがあるとします。これらのイベントをダウンロードして処理 (呼び出しstream->on_read_fn();
など) する T1 と、ユーザー コード、ユーザー イベントなどを単純に実行する T2 です。
T2 がファイル記述子を閉じたい場合は、単純に閉じてclose(stream->fd);
、T1 はその fd の I/O アラートを受信しなくなるため、stream
そこで構造体の割り当てを解除しても安全です。
しかし、T1 スレッドが、現在処理しているイベントのベクトルに同じファイル記述子を既にダウンロードしており、そのファイル記述子をまだ処理していない場合はどうでしょうか?
T1 が T2 の前にスケジュールされている場合は問題ありませんが、T2 が T1 の前にスケジュールされている場合は、ファイル記述子を閉じてstream
構造体の割り当てを解除するため、スレッド T1 は、そのファイル記述子を処理するときに、ユーザーに関連付けられたポインターを持ちます。 、既に割り当て解除された構造を指しています! もちろん、これはひどくクラッシュします。
要点は、スレッド T1 がその特定のファイル記述子の I/O アラートをダウンロードしたかどうかを T2 が知ることはなく、T2 も T1 が I/O アラートをダウンロードするかどうかを予測できないことです。
これは非常にトリッキーで、頭がくらくらします。何かご意見は?このシナリオでユーザー指定のポインターを安全に解放できるのはいつですか?
注: 私の友人は、ファイル記述子を呼び出す前に、epoll/kqueue キューからファイル記述子を削除することを提案しclose(2)
ました。これは正しく、これは私が今行っていることですが、T2 は epoll/kqueue キューからファイル記述子を削除できるため、問題は解決しませんが、そのファイルの I/O イベントは保証されません。記述子はまだカーネルから「ダウンロード」されておらず、すぐにスレッド T1 によって処理されます。