何度も質問ばかりですみません。
funcという関数をワーカースレッドを用いて処理しようとしています。
AfxBeginThreadでfuncを呼ぶプログラムを作成する前に、
テストの意味をかねて直接funcを実行してみました。
その場合は、マルチスレッドじゃないのでコマンドは受け付けませんが、
期待通りの処理が行われました。
なのでAfxBeginThreadから呼んでも大丈夫だろうと思ってコーディングすると、
func関数の内容は変更していないのにAccess Violationエラーが発生してしまいます。
スレッド内でファイルの開閉を行っているのですが、そこでエラーになっているみたいです。
エラーの原因等が全く検討つきません。
なにとぞご教授ください。お願いいたします。
以下にプログラムの大まかな流れを表示します。
---------------------------
void CtestDlg::OnButton()
{
...
XXX x(path); // コンストラクタでpathで指定されたファイルをオープン
AfxBeginThread(Thread , &x); // スレッド開始
// Thread(&x); // ←この記述なら正常に動作する
...
}
--------------------------
class XXX
{
public:
CStdioFile cf;
XXX(CString path)
{
cf.Open(path,CFile::modeRead);
}
...
};
--------------------------
UINT Thread(LPVOID pParam)
{
XXX* x = (XXX*)pParam;
...
x->cf.Close(); // ←ここでAccess Violationエラーになってるっぽい
return 0;
}
すみません、環境はWindows2000Pro、VC++6.0(MFC)です。
よろしくお願いいたします。
xの寿命の問題です。
シングルスレッドの場合、xが生成されThread()が呼ばれ処理が完了した後にxが破棄され
ます。
マルチスレッドの場合、xが生成され別スレッドが起動し、Thread()が終了したかどうか
に関係なく、xが破棄されます。
xが破棄された後、別スレッドでxが参照(x->cf.Close(); )された場合、当然アクセス
違反で終了します。
上記の記述だと、xはOnButtonのローカル変数なので、OnButtonを抜けた瞬間に
無効になります。このため、非同期実行する実際のThreadが動く時点で、
xのポインタの参照先が無効になっており、不正になります。
# ためしに、OnButtonのxをstaticとかにしたら動きませんか?
# (これはあくまで問題きりわけのためのテスト用です。本来的な対応にはお勧めできま
せん)
この問題を回避するには、xをメンバ変数にするなどしてThread()が終了するまで、xが破
棄されないようにすれば良いです。
一般的には、foo さんが書かれている対応が本来的に好ましい可能性が高いです。
foo様、Ban様、返答ありがとうございます。
まずは、Ban様の指摘に従いOnButton内のクラスの宣言をstaticで行うと、
エラーになることなく正常に動作いたしました。
ちゃんと動作しているのでこのままにしたい気持ちが大きいのですが、
この対応はあまりよくない対応なのでしょうか?
また、正しい解釈かどうかわかりませんが、
OnButton関数でxをstatic無しで宣言した場合、
OnButton関数が終了してしまうとxが破棄されてしまう。
つまり、OnButton関数がThread関数よりも早く終わってしまうと、
Thread関数でxを読もうとしてもそれはもう寿命が尽きているのでエラーになる、
ということでしょうか。
なにはともあれ、かなり勉強になりました。
ありがとうございました。
メインのスレッド(ダイアログ)がサブスレッド(XXX)よりも
はやくなくなる(たとえば、×ボタンで終わらされる)のを考慮
(サブスレッドではメインスレッドのメンバ変数等の情報を使っているので)して、
AfxBeginThreadの戻り値を管理したほうがよいかも。
(OnDestoryで正常にサブスレッドが終わるようにする)
この考え方間違っていたら、ご指摘お願いします。
あーなんか、プロセスというわけじゃな
いから、メインがおわれば勝手にサブも
強制的に終わる気がしてきた。
インターフェースの変更が許されるのならば
スレッド内でクラスXXXを生成、破棄したほうが自然です。
複数のスレッドでクラスを参照するときはスレッドセーフかどうかの考慮がいる?ので
pathもCStringではなくcharのが個人的にはオススメです。
void CtestDlg::OnButton()
{
...
char szPath[]; <-こいつにpathをコピー
...
AfxBeginThread(Thread , szPath); // スレッド開始
...
}
--------------------------
UINT Thread(LPVOID pParam)
{
XXX x pParam;
...
x->cf.Close(); // ←ここでAccess Violationエラーになってるっぽい
return 0;
}
編集中に誤って送信してしまいました。
上のThread()はまちがいです。
ごめんなさい。
--------------------------
UINT Thread(LPVOID pParam)
{
char* path = (char*)pParam;
XXX(path);
...
x.cf.Close(); // ←ここでAccess Violationエラーになってるっぽい
return 0;
}
> あーなんか、プロセスというわけじゃな
> いから、メインがおわれば勝手にサブも
> 強制的に終わる気がしてきた。
強制的に終わらせるのでメモリーリークを引き起こします。
なので、blueさんと同意見です。
>(OnDestoryで正常にサブスレッドが終わるようにする)
グローバルの変数、もしくは構造体かクラスを作ってスレッド上で
動かすてもあると思います。
競合に気をつけなければいけませんが........
確認ですが、本当に「ちゃんと」動いてますか。
どちらが先に終るかわからなかったので、かならずxが残るように、
そこだけを見て、xの寿命を延ばしてみただけなんですけど。
XXXのコンカレンシ次第で別のバクがでるはずで(憶測ですが確率はかなり高そう)、
私にはXXXの正体もわかりませんし、意味的な妥当性やシステムの整合性は未検証ですよ。
これらを正しく検討した上での判断なら大丈夫なことも(適することさえ)ありますが、
「とりあえず動いた」だけだと、頻度/確率的にはかなり部が悪い賭けじゃないかと思いますが。
staticはインスタンスが一つしか持てませんので、多分目的にはあわないんじゃないですか?
であれば、バグ持ちのやっつけ対応はやめておくが吉と思います。
元のコードの単純なstaticだと、(xの実処理にもよりますが)ボタンの連打
(スレッドが同時に複数走る状態)でおかしくなるのでは?
排他制御もなさそうなので、それで多分レースコンディションになります。
対策として、メンバ変数+排他がいいか、動的生成がいいかは、スレッドの実際の用途によるので、
今の情報ではわかりかねます。
なお、メインが終ればサブも終ります。
ただし、終了と参照がレースコンディションになって危険なので、
待ち合わせはしたほうがいいと思います。
この場合、サブスレッドが長い(or終らない)処理だと
終了時にメインが固まるので、
スレッドに中断機構を推奨します。
また、自プロセスのハンドルやメモリはOSにより解放されますのでリークはケアされます。
とはいえ外部リソースがあれば漏れますので、結局注意は必要ですが。
> XXXのコンカレンシ次第で別のバクがでるはずで(憶測ですが確率はかなり高そう)、
> 私にはXXXの正体もわかりませんし、意味的な妥当性やシステムの整合性は
> 未検証ですよ。
危険ですね。
僕は、XXXは「CMainFrame」等のメインスレッドにします。
もちろん、スレッドもここで作成する。
XXX *x;
をグローバルで作成して、
xxx()
{
x = this;
}
UINT Thread(LPVOID pParam)
{
XXX* x = (XXX*)pParam; ---->削除
...
pParamは、メインスレッドとのパラメ-タ設定のみ使用する。
x->cf.Close(); // ←ここでAccess Violationエラーになってるっぽい
ウインドウ操作に関する関数は「x->」から起動しないこと。
どこかで HWND=NULLの例外が起こる。
return 0;
}
>また、自プロセスのハンドルやメモリはOSにより解放されますのでリークはケアされま
す。
そうなんですか、デバックモードで見ると必ずメモリーリークのダンプが出たので危険
だと思っていますが.......
修正します(^^;
誤
> xxx()
> {
> x = this;
> }
正
xxx::xxx()
{
x = this;
}