私が管理している新しいチームでは、コードの大部分はプラットフォーム、TCPソケット、およびhttpネットワークコードです。すべてのC ++。その大部分は、チームを去った他の開発者からのものです。チームの現在の開発者は非常に賢いですが、ほとんどの場合、経験的には後輩です。
私たちの最大の問題:マルチスレッドの同時実行バグ。ほとんどのクラスライブラリは、いくつかのスレッドプールクラスを使用して非同期に作成されています。クラスライブラリのメソッドは、長時間実行されるタスクを1つのスレッドからスレッドプールにキューイングすることが多く、そのクラスのコールバックメソッドは別のスレッドで呼び出されます。その結果、誤ったスレッドの仮定に関連する多くのエッジケースバグがあります。これにより、同時実行性の問題から保護するための重要なセクションとロックを保持するだけでなく、微妙なバグが発生します。
これらの問題をさらに困難にしているのは、修正の試みがしばしば間違っていることです。チームが(またはレガシーコード自体で)試みようとしているミスには、次のようなものがあります。
よくある間違い#1-共有データをロックするだけで同時実行の問題を修正しますが、メソッドが期待される順序で呼び出されない場合に何が起こるかを忘れます。これは非常に簡単な例です:
void Foo::OnHttpRequestComplete(statuscode status)
{
m_pBar->DoSomethingImportant(status);
}
void Foo::Shutdown()
{
m_pBar->Cleanup();
delete m_pBar;
m_pBar=nullptr;
}
そのため、OnHttpNetworkRequestCompleteの実行中にShutdownを呼び出すことができるバグがあります。テスターはバグを見つけ、クラッシュダンプをキャプチャし、バグを開発者に割り当てます。彼は次のようにバグを修正します。
void Foo::OnHttpRequestComplete(statuscode status)
{
AutoLock lock(m_cs);
m_pBar->DoSomethingImportant(status);
}
void Foo::Shutdown()
{
AutoLock lock(m_cs);
m_pBar->Cleanup();
delete m_pBar;
m_pBar=nullptr;
}
上記の修正は、さらに微妙なエッジケースがあることに気付くまでは問題ありません。OnHttpRequestCompleteがコールバックされる前にシャットダウンが呼び出されるとどうなりますか?私のチームが持っている実際の例はさらに複雑であり、コードレビュープロセス中にエッジケースを見つけるのはさらに困難です。
よくある間違い#2-盲目的にロックを終了し、他のスレッドが終了するのを待ってからロックを再入力することで、デッドロックの問題を修正します。
よくある間違い#3-オブジェクトは参照カウントされますが、シャットダウンシーケンスはポインターを「解放」します。しかし、まだ実行中のスレッドがそのインスタンスを解放するのを待つことを忘れています。そのため、コンポーネントは完全にシャットダウンされ、その後、コールを予期しない状態のオブジェクトでスプリアスまたはレイトコールバックが呼び出されます。
他のエッジケースもありますが、一番下の行はこれです:
マルチスレッドプログラミングは、頭のいい人でも簡単です。
これらの間違いを見つけると、より適切な修正を開発するために各開発者とエラーについて話し合うことに時間を費やします。しかし、「正しい」修正には触れなければならない膨大な量のレガシコードのため、各問題の解決方法についてしばしば混乱していると思われます。
私たちはすぐに出荷するつもりであり、私たちが適用しているパッチは今後のリリースに適用されると確信しています。その後、コードベースを改善し、必要に応じてリファクタリングする時間があります。すべてを書き直す時間はありません。そして、コードの大部分はそれほど悪くはありません。しかし、スレッドの問題を完全に回避できるように、コードをリファクタリングしたいと考えています。
私が検討しているアプローチの1つはこれです。重要なプラットフォーム機能ごとに、すべてのイベントとネットワークコールバックがマーシャリングされる専用の単一スレッドを用意します。メッセージループを使用したWindowsでのCOMアパートメントスレッドに似ています。長時間のブロッキング操作はワークプールスレッドにディスパッチされる可能性がありますが、コンポーネントのスレッドで完了コールバックが呼び出されます。コンポーネントは同じスレッドを共有することさえできます。その後、スレッド内で実行されるすべてのクラスライブラリは、単一のスレッド化された世界を想定して記述できます。
その道を進む前に、マルチスレッドの問題に対処するための他の標準的な手法や設計パターンがあるかどうかにも非常に興味があります。そして、私は強調しなければなりません-ミューテックスとセマフォの基本を説明する本を超えた何か。どう思いますか?
また、リファクタリングプロセスに向けた他のアプローチにも興味があります。次のいずれかを含む:
スレッド周辺のデザインパターンに関する文献または論文。ミューテックスとセマフォの紹介を超えたもの。大規模な並列処理も必要ありません。他のスレッドからの非同期イベントを正しく処理するようにオブジェクトモデルを設計する方法だけです。
さまざまなコンポーネントのスレッド化を図式化する方法。これにより、ソリューションの研究と発展が容易になります。(つまり、オブジェクトおよびクラス全体のスレッドを議論するためのUML同等物)
マルチスレッドコードの問題について開発チームを教育します。
あなたならどうしますか?