マルチスレッドの洗礼を受ける
BorlandDeveloperStudio2006 を使って開発している仕事のソフトの話ですが、起動時に毎回読み込むファイルがかなり大きいため、ロード中にしばらくアプリが固まるという問題がありました。これを修正するため、先日からロード部分をマルチスレッド化し、進捗率を表示する作業を行っていました。
恥ずかしながらマルチスレッドについて自分で設計した経験がほとんどなく、開発環境のヘルプとマルチスレッドにおいて気をつけるべき点についての資料を頼りにおっかなびっくり状態で書いていました。複数のスレッドからアクセスする可能性のある部分は極力絞り(進捗率と終わったかどうかのフラグだけ)、そこをきっちりクリティカルセクションで管理するようにしていました。
が、実行するとたまに落ちる現象が起きてしまいました。それも特定の環境で。
落ちている箇所を調べると、C++ 標準ライブラリの iostream や locale における数箇所です。(落ちる箇所実行するたびに変わりますし、たまに落ちないこともあります)
なんだかイヤな感じがしたので以下の短いテストプログラムを BorlandDeveloperStudio2006 でビルド・実行した所、案の定再現しました。
#include <windows.h> #include <process.h> #include <iostream> #include <sstream> HANDLE g_handleThread = 0; unsigned int __stdcall processThread(void* pParam) { std::wostringstream osstr; for (unsigned int i = 0; i < 100000; i ++) { osstr << i * 2 << L" " << std::endl; } return 0; } int main(int argc, char* argv[]) { std::locale::global(std::locale(std::locale::classic(), "", std::locale::ctype)); unsigned int idThread = 0; SECURITY_ATTRIBUTES attrSecurity = { sizeof(SECURITY_ATTRIBUTES), 0, TRUE }; g_handleThread = reinterpret_cast<void*>( ::_beginthreadex(reinterpret_cast<void*>(&attrSecurity), 4096, processThread, (void*)NULL, 0, &idThread)); { std::wostringstream osstr; for (unsigned int i = 0; i < 100000; i ++) { osstr << i << L" " << std::endl; } } if (g_handleThread) { ::WaitForSingleObject(g_handleThread, INFINITE); ::CloseHandle(g_handleThread); g_handleThread = 0; } return 0; }
メインスレッドと、別のスレッドの両方で ostream を使っていますが、同じものをアクセスすることはありません。
色々な PC で試してみると落ちるパターンが見えてきました。
Pentium4 2.4CGHz (HyperThreading 有効) 落ちた環境 CoreDuo L2300 (1.5GHz, デュアルコア) Athlon64X2 3800+ (2.0GHz, デュアルコア) 全く落ちなかった環境 Pentium4 2.4BGHz (HyperThreading 未対応)
要は論理プロセッサの数が2以上の、マルチスレッドの利点を生かせる環境ですね。てことはマルチスレッド化が全然できてないということ…。orz
上記のコードでは何もトリッキーなことはしていませんし、VisualStudio2005 や C++Builder6 では上記のどの環境でも全く落ちません。
ということで、まさかの BorlandDeveloperStudio2006 の C++ 標準ライブラリの問題…を疑う事態になってしまいました。
完全に困ってしまったため、mixi の C++Builder コミュの方々の力をお借りしました。現象自体は皆さんの環境でも再現するようで、どうも C++Builder6 から BorlandDeveloperStudio2006 へのバージョンアップで標準ライブラリの実装が STLport から Dinkumware へ切り替えられた時に、マルチスレッド対応になっていないのかも*1…との仮説が出されました。特に Allocator 周りが怪しいかも…とのことで、そうなるとマルチスレッド実行時では STL 全滅やん…。orz
…泣いても仕方ないので、とりあえず QualityCentral にバグレポート(#31765)を出しました。現状で最も現実的な解決策は、「該当部分に限って STL からの脱却を図る」ということになりますが、自分のコードはほぼ全域に渡って STL に依存しまくっているため、作業量がシャレになりません。
ということで、マルチスレッド化は一旦あきらめ、次の Update が出るまで見守ることにしました。
あぁ何だかもう疲れたよパトラッシュ…。orz これがマルチスレッドの洗礼というやつなのか…。(<たぶん違う)
BorlandDeveloperStudio2006 に移行して以来、なんだかここしばらく立て続けに地雷(id:logion:20060227, id:logion:20060207, id:logion:20060209#p2)を踏んでいるような気もするのですが、一度踏んだ地雷はきっちり報告しておかないと後が大変やしね…。C++Builder6 を使い始めた時はすでに安定した時期でただただ恩恵を受けるだけ(id:logion:20041105#p1)やったから、ここらへんでちょっとでも恩返しになればいいか、と思うことにしようそうしよう…。
*1:Dinkumware 自体はマルチスレッド・シングルスレッド両方対応しているそうで、インストール時にどちらを選ぶか設定できるそうです。