コードに隠されている並行性とエラーパターン:デッドロック

確かに、デッドロックや競合状態などの言葉を聞いて、実際に誰かが会った。 これらの概念は、並行性の使用におけるエラーとして分類されます。 デッドロックとは何かを質問すると、疑いなく古典的なデッドロックの絵や擬似コードでの表現を描き始める可能性が非常に高くなります。 このようなもの:







この情報は研究所で入手します;インターネット上の本や記事で見つけることができます。 たとえば、2つのミューテックスを使用するこのようなデッドロックは、コード内で見られます。 しかし、ほとんどの場合、すべてがそれほど単純ではなく、通常の形式で表示されない場合、誰もがコード内の古典的なエラーパターンを見ることができるわけではありません。







StartUpdate、CheckAndUpdate、Stopメソッドに興味があるクラスを考えてみましょう。C++が使用され、コードは可能な限り単純です。



std::recursive_mutex m_mutex; Future m_future; void Stop() { std::unique_lock scoped_lock(m_mutex); m_future.Wait(); // do something } void StartUpdate() { m_future.Wait(); m_future = Future::Schedule(std::bind(&Element::CheckAndUpdate, this), std::chrono::milliseconds(100); } void CheckAndUpdate() { std::unique_lock scoped_lock(m_mutex); //do something }
      
      





提示されたコードで注意すべきこと:



  1. 再帰的ミューテックスが使用されます。 再帰的ミューテックスのキャプチャの繰り返しは、これらのキャプチャが同じスレッドで発生する場合にのみ期待につながりません。 この場合、ミューテックス免除の数はキャプチャの数に対応する必要があります。 すでに別のスレッドでキャプチャされている再帰ミューテックスをキャプチャしようとすると、スレッドはスタンバイモードになります。
  2. Future ::コールバックが渡した別のスレッドでスケジュール関数が開始されます(nミリ秒)


次に、受信したすべての情報を分析して、写真を作成します。







上記の2つの事実を考慮すると、CheckAndUpdateコールバックは常に別のスレッドで実行されるため、関数の1つで再帰的ミューテックスをキャプチャしようとすると、すでに別の関数でミューテックスのリリースが期待されていると結論づけることは難しくありません。



一見、デッドロックに関して疑わしいものはありません。 しかし、より厳密に言えば、それはすべて私たちの古典的な絵に帰着します。 機能オブジェクトが実行を開始すると、m_futureリソース、コールバックを直接キャプチャします

m_futureに関連付けられています:







デッドロックに至る手順は次のとおりです。



  1. CheckAndUpdateを実行する予定ですが、コールバックはnミリ秒後にすぐには開始されません。
  2. Stopメソッドが呼び出され、開始されます。mutexをキャプチャしようとします-リソースはキャプチャされたもので、m_futureが完了するまで待機し始めます-オブジェクトはまだ呼び出されていません、待機します。
  3. CheckAndUpdateの実行が開始されます。mutexをキャプチャしようとします-リソースはすでに別のスレッドによってキャプチャされており、リリースを待機しています。


Stop呼び出しを行うスレッドは、CheckAndUpdateが完了するまで待機し、他のスレッドは、前述のスレッドによって既にキャプチャされているミューテックスを取得するまで動作を継続できません。 それはかなり古典的な行き詰まりです。 作業の半分が完了しました-問題の原因が発見されました。



今、それを修正する方法について少し。
アプローチ1

リソースをキャプチャする手順は同じである必要があります。これにより、デッドロックが回避されます。 つまり、Stopメソッドでリソースのキャプチャの順序を変更できるかどうかを確認する必要があります。 ここでは、デッドロックのケースは完全に明らかではなく、CheckAndUpdateにm_futureリソースの明示的なキャプチャがないため、将来エラーが返されないようにするために別のソリューションを検討することにしました。



アプローチ2

  1. CheckAndUpdateでミューテックスの使用をオプトアウトできるかどうかを確認します。
  2. 同期メカニズムを使用するため、一部のリソースへのアクセスを制限します。 おそらく、これらのリソースをアトミックに再作成するだけで十分であると思われます(以前と同様)。アクセスは既にスレッドセーフです。
  3. アクセスが制限されていた変数は簡単にアトミックに変換できるため、前述のミューテックスは正常に削除されました。




このエラーのパターンに簡単に減少する明白なデッドロックを持つこのような簡単な例はここにあります。 最後に、信頼できるスレッドセーフなコードを書いて欲しいと思います!



All Articles