やや大規模なコードベースを改善/拡張し、それにマルチスレッドを導入しました。しかし、デッドロックを引き起こす可能性のあるコードを導入する可能性があるため、ブラックボックステストだけでテストするのはほとんど不可能ですが、少なくともコードが正しいことを再確認したいと思います。一度に複数のロックをロックしたり、常に同じ順序で複数のロックをロックしたりするなど、私はすでにベストプラクティスを遵守しています。しかし、コードが複雑な方法でループバックして、これを実施するのは難しい。
これを実現する方法は3つあります。
しかし、これはいくつかのやり取りを見逃してしまう可能性があると感じています。また、現在のコードベースが正しいことが証明されている場合でも、数週間/月のさまざまな開発者による調整により、すべてのメモと証明が再び陳腐化し、新しいレビューサイクルが必要になるため、非常に実用的ではありません。
私はすでにいくつかのツールを試しましたが、それらはNiceコールグラフを生成しますが、どの関数が何をロックしているか、それがコールグラフを通してどのようにバブルアップするかを確認するには、依然として手動の検査が必要です。繰り返しになりますが、上記のオプションと同様に、コードベースを変更し、チェックを再度やり直す必要があります。
すべての一意のロックは独自のタグを取得し、複数のロックを取得する関数にはこれらすべてのタグを付ける必要があるという考えです。このようにして(潜在的に)互換性のない関数呼び出しは、痛みのように際立つはずです。
これはコードベースが自明であり、コードへの変更が新しいコードのレビューを必要としないという点で有望に見えます(もちろん、変更を行っている人がタグ付けを順守するよう注意を払っている限り)。しかし、ほとんどの関数名は非常に扱いにくくなり、確かに、それらが呼び出す低レベルのロック関数の量が原因で、多くの残骸を集めるトップレベルの名前になります。それに加えて(私の場合)一部の関数はこのようにタグ付けできない(C++コンストラクターやオーバーロードされた演算子など)。そのため、これらの構成体を無視して、明示的な「Init()」および「Copy()」関数を追加する必要があります。あらゆる所に...
誰かこれにもっと良いアプローチがありますか?または、コードレビューとベストプラクティスの順守は、私が実行できる最善の方法ですか?問題がある場合は、ここではC++を使用していますが、.Netバージョンは使用していません(リフレクションなどはありません)。
一度に複数のロックをロックすることはなく、常に同じ順序で複数のロックをロックするなど、私はすでにベストプラクティスに従っています。
さて、一度に1つのロックしか保持しない場合、2番目のケースは決して発生しません。
doが実際に複数のロックを取得する必要がある場合、ロックの数を(理想的には1つに)減らすことは、正しい順序で確実に取得するよりも優れています。
しかし、私のコードが複雑な方法でループする場合があります。
データを共有する複雑なロジックがあると、苦労することになります。
マルチスレッドコードを正しくする最も簡単な方法は、最初にデータを共有しないことです。独自のデータで非同期に実行できるコンポーネントを除外し、メッセージキューを介してそれらと通信します。
2番目に簡単な方法は、コードをタスクに分割し、std::async
またはstd::packaged_task
または類似の独自のスレッドプールを使用して実行することです。これは、コンポーネントごとのスレッドスタイルよりも一般的ですが、タスク間の依存関係に注意してください(そして、非常に細かいタスクがたくさんある場合は、スケジューリングのオーバーヘッドが支配する可能性があります)。
どちらの場合も、データのチャンクと、それを操作できるonlyコードを一緒にパッケージ化しようとしています。通信は、メッセージキューまたはプロミス/フューチャーを介して明示的に所有権を渡すことによって行われます。
canが複雑なコードの操作で共有データを正しく取得したとしても(さらに可能性は低いですがprove妥当な標準に正しいことです) 、すべてのロック、ロック解除、ストール、キャッシュミスは、期待されるパフォーマンスの向上を損ないます。
まだ行っていない場合は、厳密に [〜#〜] raii [〜#〜] を使用すると、ロックをより確定的にすることができます。何かをロックできる唯一の関数はコンストラクターであり、ロックを解除する唯一の関数はデストラクターです。
これは、「Locker」クラスを作成することになるかもしれません。そのクラスの唯一の機能は、1つのロックを管理することです。 Lockerオブジェクトを作成するとリソースがロックされ、オブジェクトを破棄すると再びロックが解除されます。関数が保護オブジェクトを使用したい場合は、その関数のローカル変数としてLockerオブジェクトを作成するだけです。 RAIIが残りを処理します。
RAIIを厳密に使用すると、特にコードが予期しないパスをたどる場合に、ロックが誤ってロックされたままになるのを防ぎます。これには例外が含まれます。
TLA + などの証明アシスタントを使用したモデリング。動作、プロトコル、アルゴリズムをモデル化でき、デッドロック、競合状態などを検索できます。Webには、 https://learntla.com/introduction などの多くのガイドがあります。
大手クラウドサービスプロバイダーはTLA +を使用してシステムを検証します—単なるテスト以上に、これを行う多くの領域で非常に不明瞭なバグを発見しました。 http://cacm.acm.org/magazines/2015/4/184701-how-Amazon-web-services-uses-formal-methods/fulltext を参照してください
スレッド安全性分析の注釈 が気になるかもしれません。モデルが適合するようにコードを構造化できれば、問題がないことを確認する安価な方法が提供されます。 (チェックもgccにあります。)