web-dev-qa-db-ja.com

クラスメンバーとしてのstd :: mutexとstd :: recursive_mutex

recursive_mutex

http://www.zaval.org/resources/library/butenhof1.html

しかし、スレッドセーフな(mutexで保護された)クラスを実装する方法を考えると、mutexで保護する必要のあるすべてのメソッドがmutexで保護され、mutexが一度だけロックされることを証明するのは非常に難しいようです。

したがって、オブジェクト指向設計の場合、std::recursive_mutexはデフォルトで、std::mutex(1つのリソースのみを保護するために)1か所でのみ使用されない限り、一般的な場合のパフォーマンス最適化と見なされますか?

明確にするために、私はプライベートな非静的ミューテックスについて話しています。したがって、各クラスインスタンスには1つのミューテックスしかありません。

各パブリックメソッドの開始時:

{
    std::scoped_lock<std::recursive_mutex> sl;
43
NoSenseEtAl

std::recursive_mutexをデフォルトにし、std::mutexをパフォーマンスの最適化と見なすべきですか?

そうでもない非再帰的ロックを使用する利点はnot単なるパフォーマンスの最適化です。つまり、コードはリーフレベルのアトミック操作が本当にリーフレベルであることを自己チェックし、他のものを呼び出さないことを意味しますロックを使用します。

あなたが持っている合理的に一般的な状況があります:

  • シリアル化する必要のある操作を実装する関数であるため、ミューテックスを取得して実行します。
  • より大きなシリアル化操作を実装し、最初の関数を呼び出してその1ステップを実行したいが、より大きな操作のロックを保持している別の関数。

具体的な例のために、おそらく最初の関数はリストからノードをアトミ​​ックに削除し、2番目の関数はリストからtwoノードをアトミ​​ックに削除します(別のスレッドにリストを表示させたくない場合があります) 2つのノードのうち1つだけを取り出しました)。

needこのための再帰的なmutexはありません。たとえば、最初の関数を、ロックを取得して「安全でない」操作を行うプライベート関数を呼び出すパブリック関数としてリファクタリングできます。 2番目の関数は、同じプライベート関数を呼び出すことができます。

ただし、代わりに再帰的なミューテックスを使用すると便利な場合があります。この設計にはまだ問題があります:remove_two_nodesremove_one_nodeを呼び出すのは、クラスの不変式が保持されない時点です(2回目に呼び出すと、リストは正確に望ましくない状態になります)露出する)。しかし、remove_one_nodeがその不変式に依存していないことがわかっていると仮定すると、これは設計の致命的な欠陥ではなく、単にすべてのクラス不変式が常に保持する理想よりも少し複雑になっただけですパブリック関数が入力されるたびに」。

そのため、このトリックは時折有用であり、この記事の範囲内で、再帰的なmutexを嫌いではありません。 Posixに含める理由は、「ミューテックスの属性とスレッド拡張を実証する」という記事の説明とは異なると主張する歴史的な知識はありません。ただし、これらをデフォルトとは考えていません。

再帰ロックが必要かどうかが設計で不明な場合、設計が不完全であると言っても安全だと思います。後でコードを書いているという事実を後悔することになりますわからないロックがすでに保持されているかどうかなど、根本的に重要なことです。そのため、「万が一に備えて」再帰的ロックをかけないでください。

必要なことがわかっている場合は、それを使用します。必要ないことがわかっている場合、非再帰的ロックの使用は単なる最適化ではなく、設計の制約を強化するのに役立ちます。 2番目のロックが失敗する方が、成功することや、設計が決して起こらないと言っていることを誤って行ったという事実を隠すよりも便利です。しかし、設計に従えば、ミューテックスをダブルロックしないと、再帰かどうかはわかりません。したがって、再帰ミューテックスは直接有害ではありません。

この類推は失敗するかもしれませんが、これを見る別の方法があります。 NULLポインターを逆参照するときにスタックトレースでプログラムを中止するものと、0を返す(または、より多くの型に拡張する:ポインターのように動作する)2種類のポインターから選択できると想像してください値で初期化されたオブジェクトを指します)。非再帰的ミューテックスは中止するものに少し似ていて、再帰的ミューテックスは0を返すものに少し似ています。両方とも潜在的に用途があります-人々は時々「静かなnot-a -value」値。しかし、nullポインターを逆参照しないようにコードが設計されている場合は、デフォルトを暗黙のうちに許可するバージョンを使用したくありません。

25
Steve Jessop

ミューテックスとrecursive_mutexの議論を直接比較するつもりはありませんが、recursive_mutexがデザインにとって絶対に重要なシナリオを共有するのは良いことだと思いました。

Boost :: asio、Boost :: coroutine(および、私はそれらにあまり精通していませんが、おそらくNT Fibersのようなもの)を扱うとき、再入可能の設計問題がなくてもミューテックスが再帰的であることが絶対に不可欠です。

その理由は、その設計によるコルーチンベースのアプローチが実行を一時停止するためですinsideルーチンとその後に再開つまり、クラスの2つの最上位メソッドは、サブ呼び出しを行わずに「同じスレッドで同時に呼び出される」可能性があります。

7
MB.