ログインしてさらにmixiを楽しもう

コメントを投稿して情報交換!
更新通知を受け取って、最新情報をゲット!

C++ Builderコミュのマルチスレッド実行時の iostream

  • mixiチェック
  • このエントリーをはてなブックマークに追加
BDS2006で開発中の自作プログラムにおいて、時間のかかるロード部分をマルチスレッド化した所、特定の環境でよく落ちるようになってしまいました。
調べてみると iostream (あるいはその中で使っている locale?)に問題があるような気がしています。

具体的には、BDS2006で下記プログラムをビルドし、論理プロセッサ数が2以上の PC で実行すると落ちることがあるようです。
(VisualStudio2005, C++Builder6 でビルドしたものはまったく問題ないのに…)

同様の現象に遭遇されたことのある方、回避方法をご存知の方がおられましたら教えて頂けると嬉しいです…。
(下記プログラムがスレッドセーフじゃない、というツッコミも歓迎です)

落ちたPC(まれに落ちないこともある)
- Pentium4 2.4CGHz(FSB800MHz, HyperThreading有効)
- CoreDuo L2300(1.5GH)

落ちなかったPC
- Pentium4 2.4BGHz(FSB533MHz, HyperThreading非対応)

(タブが効かないようなので、下記ソースは全角スペースでインデントしています)
−−−ここから−−−ここから−−−ここから−−−

#include <windows.h>
#include <iostream>
#include <sstream>

HANDLE g_handleThread = 0;

DWORD WINAPI processThread(LPVOID 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));

 DWORD idThread = 0;
 g_handleThread = ::CreateThread(NULL, 0, processThread, 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;
}

−−−ここまで−−−ここまで−−−ここまで−−−

コメント(52)

高橋です
------------------------------------------
std::ostringstream oss;
oss << "123456789012345678901234567890123";
------------------------------------------
だとCodeGuardがエラーを報告するけど
------------------------------------------
std::ostringstream oss;
oss << "12345678901234567890123456789012";
------------------------------------------
は問題無いみたいですね... 不思議です
>檜木さん
>・iostream を使うなら API の CreateThread は実はNGで_beginthread 等を使わないと正しい初期化が行われない?

うっ、これは根本的な問題ですね…。
まずはこちらに書き換えて試してみます。

言い訳にもなりませんが、マルチスレッド化の作業時は VC8 のヘルプを見ながら書いていて、そこで C ランタイムライブラリに限る(→C++ランタイムは無関係)と勘違いしていたようです…。

iostream に float を食わせた時の CodeGuard 問題は
BDS2006 トピックで書いておられましたね。

> ken さん
サポート料、調べて頂きありがとうございました。(すみません、やはり零細企業には手が出そうにないです…。orz)
_beginthread や _beginthreadex に変更して試してみましたが、やはり落ちる状況は変わりませんでした。orz
今晩にでもソースをアップしておきます。
残る可能性については、

>檜木さん
> ・BDS2006でのSTL入れ替えに伴う問題
> ・暗黙のうちに使われている Allocator がマルチスレッド動作に対応していない

Allocator がマルチスレッド非対応なら、STL 周りは全滅ですね…。(泣)
もう Borland による修正を待つくらいしかできる事はなさそうです…。

> ・マルチスレッド対応のRTL にリンクされていない

VC++ ならばよく聞く話なので、最初にまずこれを疑い
BDS2006 のオプションを探しまわったのですが、
見つけられませんでした…。orz
リンカオプションのコマンドラインをみる限りは、cw32mt.lib をリンクしているようなので、問題はなさそうなのですが…。
_beginthread/_beginthreadex 版をこちらに上げました。
http://qualia.la.coocan.jp/bcb/modules/smartsection/item.php?itemid=5
現象は変わらずですので実行しても面白味はありません(^^;)。

> 檜木さん
> 念のためと思いましたが、今回は無罪のようですね。
とはいえ、使い方を誤っていた事には違いありません。
勉強になりました。ありがとうございます(_o_)

> CodeGuard 問題は私が自分で書いていました???
> 全然覚えていません…。がっくり…。
こちらのトピックの 43 番目が該当する記事だと
思っているのですが、勘違いしてましたらすみません(^^;)。
http://mixi.jp/view_bbs.pl?id=3147945&comm_id=76027

> Dinkumware ではここで STL とマルチスレッドに言及しています。特に参考になる感じはないのですけど…

ありがとうございます。こちらも拝見しております。
Dinkumware 自体は マルチ/シングルスレッド両対応で
セットアップスクリプトで設定可能なものの、
デフォルトはマルチスレッドの方だとありますね…。
yval.h でオプションを設定可能とあるので、
ちょっとそれらしい define をいじってみましたが、
上のサンプルが落ちなくなる組み合わせを見つけることが
できませんでした…。

今後の話ですが、だんだん打つ手ナシになってきましたので、
QualityCentral の成り行きを見守りつつ、
しばらくお仕事ソースのスレッド対応は凍結しようかと考えています。
現状で唯一明確な解決策というのは「STL からの脱却」かと
思いますが、現状のコードは依存しまくっているため
修正の手間が馬鹿にならないのです…。
(皆さんに良くして頂いたのに、ヘタレな結論で本当にスミマセン… orz)
檜木さん、お気遣いありがとうございます。(_o_)
STLport を入れてみるというのは気づきませんでした。
ちょっと作業的に重そうですが(^^;)後で試してみたいと思います。

仕事締切のプレッシャーから逃れられたので、あれからボチボチ調査していますが、今のところ VisualStudio2005 の Dinkumware と BDS2006 の Dinkumware の違いに注目しています。

VS2005は
- コピーライトが 2005年
- C の関数のラッパ(cmathなど)も Dinkumware
BDS2005は
- コピーライトが 2003年
- C の関数のラッパは Borland 製

という違いがあるので、互いにソースを比較して2 年間の間に修正された所や、C 関数のラッパを呼び出している所に着目しています。

並行してデバッガ上での動作も見ていますが、落ちるタイミングが locale オブジェクトの解放時に集中しているようです。そこでリファレンスカウンタを上げ下げしているので lock し忘れかも…と思うのですが確証はまだありません。

また何かわかったら書き込みます。
高橋(智)です。
私の日記にも書きましたが、STLport 5.1RC2 がC++Builder2006で利用できるようです。
お知らせありがとうございます! 後で試してみたいと思います。
上の Dinkumware の件ですが、結局のところlocale 周りがバイナリの中に隠れてしまっており、追跡できずに詰まっております…。その点、ソースが公開されている STLport ならどこまでも追いかけられそうなので実にありがたいことです。
私の日記にも書きましたが、以下ビルドする手順です
STLport 5.1.0 RC2 を C++Builder2006(BDS2006) でビルドする手順

1) STLport-5.1.0-RC2.zip を C:\STLport-5.1.0 に展開

2) mingw32-make-3.80.0-3.exe を C:\STLport-5.1.0\mingw にインストール

3) mingw32-make.exe を gmake.exe にリネーム(好みの問題)

4) コマンドプロンプトを開く

5) 環境変数PATHを限定する
set PATH=C:\STLport-5.1.0\mingw\bin;C:\Borland\BDS\4.0\Bin;C:\WINDOWS\system32

6) 環境変数INCLUDEを指定する
set INCLUDE=C:\Borland\BDS\4.0\include

7) ilink32.cfg の先頭付近に C:\Borland\BDS\4.0\lib\psdk を追加する
-L"C:\Borland\BDS\4.0\lib\psdk";"C:\Borland\BDS\4.0\lib";"C:\Borland\BDS\4.0\lib\obj";"C:\Borland\BDS\4.0\lib\release";"C:\Borland\BDS\4.0\lib\Indy10"

8) C:\STLport-5.1.0\build\lib に移動する

9) 設定を行う
configure.bat -c bcc --rtl-dynamic

10) ビルドしてインストールする
gmake -fbcc.mak install

11) C:\STLport-5.1.0\bin にdll一式、C:\STLport-5.1.0\lib にlib一式が生成される
補足です。
C++Builder2006で STLport 5.1.0 RC2 を利用するには、プロジェクトのオプションで
・ヘッダの検索パスに C:\STLport-5.1.0\stlport を*先頭に*追加する
・リンカの検索パスに C:\STLport-5.1.0\lib を追加する
です。
高橋(智)です。

ともたこさんが提供された問題のプロジェクトを試してみました。

[ビルド環境]
Windows2000(SP4) + C++Builder2006 + 1CPU
STLは C++Builder2006付属のDinkumware または STLport 5.1 RC2
動的RTL(cc3270mt.dll)を使用した

[実行環境]
WindowsXP Pro(x64) + AMD Opteron x 2CPU + メモリ2GB

[結果]
Dinkumware版は、例外が出て落ちる
STLport5.1RC2版は、問題なし
> ken さん
動作確認ありがとうございます。
こちらは cygwin の gmake で STLport をビルドしようとして詰まっていました。(unexpected EOF ... というあたりがショボすぎ orz)

また一方で、うちの日記へのコメントで Borland C++ Runtime Library Source 12.0 の在り処(C:\Program Files\Borland\BDS\4.0\source\cpprtl)を教えて頂き、自分の目の節穴さに悶絶していたところです。

なにはともあれ、これで色々と前に進めます。本当にありがとうございました。
詳細をうちの日記に書きましたが、
Dinkumware のソースのおかげで解決策かも?というものが見つかりました。

- C:\Program Files\Borland\BDS\4.0\include\dinkumware\yyvals.h の 345行目、 _IOSTREAM_OP_LOCKS の値を 0 から 1 にする。
- C:\Program Files\Borland\BDS\4.0\source\cpprtl\readme の記述を参考に、ランタイムライブラリをビルドし直す。(Path の設定後、バッチファイルを実行するだけ)
- C:\Program Files\Borland\BDS\4.0\source\cpprtl\lib\ の下にできあがった cp32mt*.lib, cw32*.lib を C:\Program Files\Borland\BDS\4.0\Lib\ に上書きコピーする。
- 同じく cc3270*.dll を C:\WINDOWS\SYSTEM32\ に上書きコピーする。

これで問題のプロジェクトをビルドし直すと、何回実行しても落ちなくなりました。
QualityCentral のバグ報告にも追記しましたので、しっかり検証された上で次回の Update に取り込まれることを祈っています。

みなさま本当にありがとうございました!
ともたこさん
問題の修正手順、ありがとうございました。
http://qc.borland.com/wc/qcmain.aspx?d=31765
にコメントされている手順などを、Borland社内のBugTrackingシステムにも反映しました。

願わくば Hotfix で修正されると良いのですが...
> ken さん
ありがとうございます。
標準ライブラリの変更なので影響は小さくない(テストが大変そうな)気がしますが…(汗)。
こちらで以前マルチスレッド化しようとしていたアプリは、修正後のライブラリで今のところ落ちずに動いています。
ありがとうございます。
当初は「自分の勘違いでしたorz」というオチを予想していましたが、なんとか先につながりそうな落とし所が見つかってよかったです。

> STL や boost のディレクトリをプロジェクトで指定するときは IDE の環境変数の設定を使うと便利そうです。

探してみたところ「ツール→オプション」で出てくる IDE のオプションダイアログ内、「環境オプション/環境変数」ですね。常に最新版に追従し続ける時は便利そうです。
> STLport 5.1.0 RC2
STLport 5.1.0 RC3 がリリースされたようです。
正式リリースに向けて着実に進んでいるようですね。(Boost1.34はどうなんでしょうかね)

[STLport 5.1.0 RC3]
http://sourceforge.net/project/showfiles.php?group_id=146814
高橋(智)です。
このトピックとは趣旨がズレますが、STLportの5.1.1と5.1.2と続けてupdateがあったようです。まだ内容は何も確認しておりませんが... (^^;
[STLport 5.1.2]
http://sourceforge.net/projects/stlport

P.S.
Dinkumwareのiostreamの件、まだ修正されていないようですね... Hotfixが出ると嬉しいのですが...
ちょっと調べてみただけなんですけど、C++の標準ライブラリは必ずしもスレッドセーフであることが保証されていないらしいので、バグではないみたいですよ。
(すみません、文意が不明瞭でしたので書き直しました)

> ken さん
情報ありがとうございます。
VC6 や Borland コンパイラでの不具合対応とかが
入っているようですね…。

> Kaz さん
なななんですとー…。
もしよろしければ情報源を教えて頂けると嬉しいです。

もっとも、手元の C++ 規格書(*1)を "thread" で検索してみた所、
1 箇所しか出てこない上に、スレッドセーフに関する記述はなさそう…
という所までは先ほど確認しました。

規格書にないから対応不要、というのも一応筋は通っていると
思いますが、慣れ親しんだ iostream 等のライブラリを
マルチスレッド環境で使えないのはやはり不便に感じます…。

このスレの17番の記事でも書きましたが、
Dinkumware 自体は マルチ/シングルスレッド両対応で
セットアップ時に設定可能なので、Borland が何らかの理由(*2)で
意図的に OFF 設定 にしているのかも知れませんね。
それならそうで QC (#31765)の Status をさっさと
As designed(そういうもんでっせ)にしてくれたら
あきらめもつくのに…なんて思います(^^;)。
もっとも、せっかくの Dinkumware なのに勿体ないと思いますが。

STLport の方は特徴の一つとしてスレッドセーフを謡ってます
ので、そろそろ移行を考えようかと思います…。

(*1)
http://webstore.ansi.org/ansidocstore/product.asp?sku=INCITS%2FISO%2FIEC+14882%2D2003
で購入した PDF ファイルのやつです

(*2)
マルチスレッド実行にも何の問題もなかった
VC++ は C++/C いずれのライブラリにも Dinkumware を使っており
BDS は Cランタイムのみ独自のもの済ませているあたりが気になります。
C++の規格上はスレッド安全性に関して何も規定していないらしいので、スレッドセーフかどうかはコンパイラベンダー (この場合はBorland) がどうするかによるのだと思います。明記してなければ、スレッドセーフではないのではないかなあ。
 標準ライブラリをスレッドセーフにしなければならないとすると、必要のないときにもスレッドの同期が入って、処理が遅くなるおそれがあるので、アプリケーションに任せるという判断をすることもあり得ますよね。
シングルスレッド・マルチスレッドどちらの標準ライブラリを
使うのか、アプリ開発者の選択に任せるという方針は
全く正しいと思います。
VC++ もコード生成のオプションでリンクされるランタイムが
切り替わりますし…。(*1)

ただ、BDS はプロジェクト作成の時に「マルチスレッド」なる
チェックボックスがあり、チェックを入れるとリンクされる
ランタイムがマルチスレッド対応の cw32mt/cw32mt.lib に
なるにも関わらす、iostream 系は依然非対応…というのは
整合性が取れてないと思うのです…。

#31765 の Status が Opened のままなのは、
きっとそのうち対応しようとしてくれている…と
信じて待ちたいと思います。

(*1) 最も、VisualStudio2005 からはマルチスレッドのみに
なってしまいましたが…。
マルチスレッドでもiostreamは1スレッドからしか使わないという使い方も考えられるので、ロックするだけ無駄ということもありますよ。関数呼び出しのたびにロックするより、まとめて排他処理するほうが効率的ですしね。
iostream の使用をシングルスレッドに限定する使い方もアリ、というのは一応理解できるのですが…、
それをデフォルトに定めてしまうのはどうなんでしょう…。

設定を変更するにもC++ランタイムの再ビルドが必要だった(このスレの26番の記事参照)訳ですし、Borland が本当にその方針を採用して出荷していたのなら、せめてヘルプか C++ Runtime の readme(C:\Program Files\Borland\BDS\4.0\source\cpprtl\readme)に書いておいて欲しかった…と思うのは贅沢でしょうか…。
Borlandは多分何も考えてないと思うけど。

(Dinkumware本家のスレッド安全性に関する説明)
http://www.dinkumware.com/manuals/?manual=compleat&page=thread_safety.html

あと、C++Builderのマニュアルを調べていて、System::IsMultiThreadを真にしないとメモリ管理がスレッドセーフにならないという説明があったんですが、元のプログラムでこれをしたら問題なく動いたりしませんかね?
> System::IsMultiThread
c++では、マルチスレッド版でビルドする際には自動的にTrueになるようになっていると思います。
Delphiでも必要に応じて(ユニットをusesすると)自動的にTrueになるようになっていると思います。
> c++では、マルチスレッド版でビルドする際には自動的にTrueになるようになっていると思います。

どうやらそのようですね。実際に確認しました。
 それならマニュアルに書くなよと言いたいところですが、それはそれとして。
 Dinkumwareによると、_IOSTREAM_OP_LOCKSはマルチスレッド時の競合を排除するためのものではないようなので、多分別の原因があるんじゃないかと思いますけどね。
> Atomic operations on these objects, such as extractions and insertions, are protected against simultaneous access from different threads. (中略) Thus, you can safely share iostream objects across threads without worrying about loss of integrity.

ということなので、別々のスレッドから同時にアクセスしても保護されるということですよね。

> If you want changes to the file to be reflected after each atomic write, you must change the definition of the macro _IOSTREAM_OP_LOCKS from 0 to 1 (後略)

iostreamでの書き込みが即座にファイルに反映されるようにするには_IOSTREAM_OP_LOCKSを0から1に変えろと書いてますので、これが競合回避のためのものとは思えません。
 マルチスレッド対応かどうかは別のマクロMT/NO_MTで切り替えてますし。
同じ箇所を読み直していて自分のミスに気づいたので
44番を削除しましたが、すでにレスを付けて
下さっていたのですね…。大変失礼しました。
なんか以下の部分が怪しそうですね。_MSC_VERが定義されている場合はout-of-lineの関数を使うのに対して、Borlandでは当然このマクロを定義しないので、/* non-Windows multithreading */のほうを使ってしまいます。_MAYBE_LOCKの定義を見れば、ロケールのロックがかからないのは明らかなので、ロケール関係がロックなしで使われてしまうことになります。

#elif defined(_WIN32_WCE) || defined(_MSC_VER)
#define _LOCKIT(x) lockit x

explicit _Lockit(); // set default lock
explicit _Lockit(int); // set the lock
~_Lockit(); // clear the lock

private:
int _Locktype;

#else /* non-Windows multithreading */
#define _LOCKIT(x) lockit x

explicit _Lockit()
: _Locktype(_LOCK_MALLOC)
{ // set default lock
_MAYBE_LOCK
_Locksyslock(_Locktype);
}

explicit _Lockit(int _Type)
: _Locktype(_Type)
{ // set the lock
_MAYBE_LOCK
_Locksyslock(_Locktype);
}

~_Lockit()
{ // clear the lock
_MAYBE_LOCK
_Unlocksyslock(_Locktype);
}

private:
int _Locktype;
#endif /* _MULTI_THREAD */
調査ありがとうございます。(_o_)

前回調べた時に なぜ _IOSTREAM_OP_LOCKS に行きついたかどうかを思い返していました。
以下は 全て C:\Program Files\Borland\BDS\4.0\include\dinkumware\yval.h からの引用です。

−−−ここから−−−ここから−−−ここから−−−
class _CRTIMP2 _Mutex
{ // lock under program control
public:

#if !_MULTI_THREAD || !_IOSTREAM_OP_LOCKS
void _Lock()
{ // do nothing
}

void _Unlock()
{ // do nothing
}

#else /* !_MULTI_THREAD || !_IOSTREAM_OP_LOCKS */
_Mutex();
~_Mutex();
void _Lock();
void _Unlock();

private:
_Mutex(const _Mutex&); // not defined
_Mutex& operator=(const _Mutex&); // not defined
void *_Mtx;
#endif /* !_MULTI_THREAD || !_IOSTREAM_OP_LOCKS */

};
} // extern "C++"

−−−ここまで−−−ここまで−−−ここまで−−−

こちらも、_MULTI_THREAD と _IOSTREAM_OP_LOCKS の両方が 1 に
定義されていないと、この Mutex の _Lock()/_Unlock() の中身が
空になってしまいます。

_MULTI_THREAD は、yval.h の内部で以下の手順で 1 に定義されるので問題ありません。
- コンパイルオプションに -tWM を指定されていると __MT__ が定義される
- __MT__ が定義されていると _NO_MT は無変更(されてなければ 1 になる)
- _NO_MT が 1 でなければ _MULTI_THREAD を 1 に定義

また、yval.h の冒頭には
#define _IOSTREAM_OP_LOCKS 0 /* 0 for no iostream locks, 1 for atomic */
とあるのですが、Dinkumware のドキュメントと微妙に意味が違うような気がするのは気のせいでしょうか…。
_Mutexはソースコードを検索しても使ってるところが見つからなかったので、もしかしたら関係ないかも。
 _MAYBE_LOCKは、_IOSTREAM_OP_LOCKSが0のとき_LOCK_MALLOCと_LOCK_DEBUG?だけロック、1のときすべてロック (条件なし) という定義なので、_MAYBE_LOCKの定義を_LOCK_LOCALEでもロックするように変更して問題が解消するようなら、ここの移植忘れだと思われます。
 ていうか、non-Windows multithreadingってLinuxのことなんですかね?
検索してみたら、_Mutex は C:\Program Files\Borland\BDS\4.0\include\dinkumware\streambuf で使われているようです。

_LOCK_LOCALE は確かに有効そうですね。帰宅したら試してみたいと思います。

> ていうか、non-Windows multithreadingってLinuxのことなんですかね?
どうもそんな気がしています。
今回の件とは多少話が反れますが、C:\Program Files\Borland\BDS\4.0\include\dinkumware\xlocinfo の一行目をみる限り、どうも Linux 用のヘッダを流用しているようなのです…。
別件のバグ(QC# 32177)を追いかけていた時に見つけたのですが、実装内容も含めて脱力してしまいました…。
(当時の様子: http://d.hatena.ne.jp/logion/20060806#p1)
yval.h の _MAYBE_LOCK のマクロを以下のように変更して試してみました。(このマクロは C++ランタイムソースでは使われていないので、C++ランタイムの再ビルドはしていません)

・変更前−−−−
#define _MAYBE_LOCK \
if (_Locktype == _LOCK_MALLOC || _Locktype == _LOCK_DEBUG)

・変更後−−−−
#define _MAYBE_LOCK \
if (_Locktype == _LOCK_MALLOC || _Locktype == _LOCK_DEBUG || _Locktype == _LOCK_LOCALE)

結果は、10回中 5回の割合で落ちるようです…。orz

ログインすると、残り19件のコメントが見れるよ

mixiユーザー
ログインしてコメントしよう!

C++ Builder 更新情報

C++ Builderのメンバーはこんなコミュニティにも参加しています

星印の数は、共通して参加しているメンバーが多いほど増えます。

人気コミュニティランキング