web-dev-qa-db-ja.com

スレッドがロックされたリソースを所有できる時間を制御するメカニズムまたはミューテックスを実装するC ++デザインパターンはありますか?

スレッドが特定のリソースをロックするたびに、特定の期間後にそのリソースを強制的に解放することを保証する方法を探しています(まだ解放されていない場合)。特定のスレッドがその接続を所有できる時間を制限する必要がある接続を想定します。

私はこれがどのように使用されるかを想像しています:

{
    std::lock_guard<std::TimeLimitedMutex> lock(this->myTimeLimitedMutex, timeout);
    try {
        // perform some operation with the resource that myTimeLimitedMutex guards. 
    }
    catch (MutexTimeoutException ex) {
        // perform cleanup
    }
}

ロックを取得できない場合にプログラムをタイムアウトさせるtimed_mutexがあることがわかります。タイムアウトが発生する必要がありますロックが取得されます。

予想外に持ち去られる可能性のあるリソースを取得する状況はすでにいくつかあります。たとえば、tcpソケット-ソケット接続が確立されると、両側のコードは、反対側が接続をドロップした場合を処理する必要があります。

通常は自分でタイムアウトするタイプのリソースを処理するパターンを探していますが、そうでない場合はリセットする必要があります。これは、すべてのタイプのリソースを処理する必要はありません。

16
Jay Elston

これは機能せず、機能しません。つまり、これを行うことはできません。これは、所有権とアトミックトランザクションのすべての概念に反します。スレッドがロックを取得して2つのトランザクションを続けて実装するとき、スレッドはWordの外部からアトミックに見えるようになると期待します。このシナリオでは、トランザクションが破損する可能性が非常に高くなります。トランザクションの最初の部分は実行されますが、2番目の部分は実行されません。

さらに悪いことに、ロックが強制的に削除されるため、中断されたスレッドがロールバックする前に、部分的に実行されたトランザクションがWordの外部から見えるようになります。

この考え方は、マルチスレッド思考のすべての学校に反しています。

32
SergeyA

SergeyAsの回答をサポートします。タイムアウト後にロックされたミューテックスを解放することは悪い考えであり、機能しません。 Mutexは相互排除を意味し、これは違反できないロックハードコントラクトです。

しかし、あなたはあなたが望むことをすることができます

問題:スレッドが特定の時間Tより長くmutexを保持しないことを保証する必要があります。

ソリューション:ミューテックスを時間Tより長くロックしないでください。代わりに、絶対に必要な操作に対してのみミューテックスがロックされるようにコードを記述してください。そのような時間Tを与えることは常に可能です(もちろん、マルチタスクおよびマルチユーザーオペレーティングシステムでの不確実性と制限を法として)。

それを実現するには(例):

  • ロックされたセクション内でファイルI/Oを実行しないでください。
  • Mutexがロックされている間は、システムコールを呼び出さないでください。
  • Mutexがロックされている間はリストをソートしないでください(*)。
  • Mutexがロックされている間(*)、リストの各要素で低速の操作を実行しないでください。
  • Mutexがロックされている間はメモリの割り当て/割り当て解除を行わない(*)。

これらのルールには例外がありますが、一般的なガイドラインは次のとおりです。

  • コードをわずかに最適化しない(たとえば、クリティカルセクション内で冗長コピーを行う)ことで、クリティカルセクションをできるだけ短くします。これは優れたマルチスレッドプログラミングです。

(*)これらは、リスト全体をロックし、操作を実行してからリストのロックを解除したくなるような操作の例にすぎません。代わりに、理想的にはほとんどのSTLコンテナーが提供するswap()操作を使用して、ミューテックスがロックされている間にリストのローカルコピーを取り、元のリストをクリアすることをお勧めします。そして、クリティカルセクションの外のローカルコピーに対して低速の操作を実行します。これは常に可能とは限りませんが、常に検討する価値があります。最悪の場合、並べ替えは複雑さが二乗し、通常はリスト全体にランダムにアクセスする必要があります。クリティカルセクションの外でリスト(のコピー)を並べ替え、後で要素を追加または削除する必要があるかどうかを確認すると便利です。メモリの割り当ては、その背後にもかなりの複雑さがあるため、大量のメモリの割り当て/割り当て解除は避ける必要があります。

15

C++だけでそれを行うことはできません。

Posixシステムを使用している場合は、それを実行できます。タイムアウトするスレッドに対してのみマスクされていないSIGALARMシグナルをトリガーする必要があります。シグナルハンドラーでは、フラグを設定し、longjmpを使用してスレッドコードに戻る必要があります。スレッドコードのsetjmp位置では、信号がトリガーされた場合にのみ呼び出すことができるため、Timeout例外をスローできます。

その方法については この答え を参照してください。

また、Linuxでは seems シグナルハンドラから直接スローできます(したがって、ここではlongjmp/setjmpは使用できません)。

ところで、私があなただったら、私は反対をコーディングします。考えてみてください:「ねえ、あなたは時間がかかりすぎているので、これまでに行った(長い)作業をすべて捨てて、私が進歩できるようにしましょう」とスレッドに伝えたいとします。理想的には、「ABCDタスクのAを実行したので、他のユーザーがAで進行できるようにミューテックスを解放してみましょう。次に、Bを実行するために再度実行できるかどうかを確認してみましょう。など。」おそらくもっときめ細かくしたい(小さいオブジェクトではより多くのミューテックスを持っているが、同じ順序でロックしていることを確認する)か、RWロックを使用します(変更しない限り他のスレッドがオブジェクトを使用できるようにするため)。等...

5
xryl669

ミューテックスの所有者は、トランザクションの途中で無効な状態のままになっているものをクリーンアップする機会を必要とするため、このようなアプローチは強制できません。これには、不明な任意の時間がかかる場合があります。

典型的なアプローチは、長いタスクを実行しているときにロックを解放し、必要に応じて再取得することです。誰もがわずかに異なるアプローチを持っているので、あなたはこれを自分で管理する必要があります。

この種の行為が受け入れられる慣行がわかっている唯一の状況は、特にマイクロコントローラー(カーネルがないか、またはすべてのカーネルであるかどうか)に関して、カーネルレベルです。コールスタックを変更する割り込みを設定して、トリガーされたときに興味のある特定の操作を巻き戻すことができます。

1
Cort Ammon

「条件」変数にはタイムアウトを設定できます。これにより、スレッドが自発的にリソースを解放するまで(notify_one()またはnotify_all()を使用して)待機できますが、待機自体は指定された固定時間後にタイムアウトします。

「条件」のBoostドキュメントの例 は、これをより明確にするかもしれません。

強制的にリリースしたい場合は、強制的にリリースするコードを記述する必要があります。これは危険かもしれません。 C++で書かれたコードは、かなり金属に近いものを実行できます。リソースが実際のハードウェアにアクセスしていて、何かが完了するのを待っている可能性があります。プログラムがスタックしているものはすべて物理的に終了できない場合があります。

ただし、可能であれば、wait()がタイムアウトしたスレッドで処理できます。

0