- PR -

C#のLock処理について

投票結果総投票数:8
Windows 8 100.00%
  • 投票は恣意的に行われます。統計的な調査と異なり、投票データの正確性や標本の代表性は保証されません。
  • 投票結果の正当性や公平性について、@ITは一切保証も関与もいたしません。
投稿者投稿内容
なちゃ
ぬし
会議室デビュー日: 2003/06/11
投稿数: 872
投稿日時: 2006-03-23 19:19
private bool flagA = false;
private bool flagB = false;
ここら辺の定義にvolatileつけてみたり。

--追記
やっぱり関係なさそうかな…


[ メッセージ編集済み 編集者: なちゃ 編集日時 2006-03-23 19:55 ]
Bob
常連さん
会議室デビュー日: 2006/03/23
投稿数: 31
投稿日時: 2006-03-23 19:40
引用:

ってどうやって確認してるんでしょうか?
というか、何を見て消えた、紛失した、と判断しているんでしょうか?



結局、flagAとflagBは順次「true」の設定され、排他制御がかかっているので、CallAnotherMethodが呼ばれなかったことから、どこかで、消えてしまったと推測したのですが、この辺り、別の可能性もございましたら、教えて頂きたいです。
Jitta
ぬし
会議室デビュー日: 2002/07/05
投稿数: 6267
お住まい・勤務地: 兵庫県・海手
投稿日時: 2006-03-23 22:09
> // ProcessBには、UnManagedCodeの内容があり、
> // 終わりにGC.Collect()が必ず行われる
ムダ。

 GC.Collect は、マネージドメモリのガベージをコレクトします。マネージされていないものをコレクトするわけではありません。
(っつうか、マネージしているからコレクトできるわけで、マネージしていないものをどうやって知るのさ?)
 例えば、エクセルを COM 連携で起動したとき、Quit しないとエクセルプロセスは残ります。ガベージコレクトを行っても、「プロセス終了」を行わないと、プロセスが終了しないというリークが発生することになります。

コード:

switch(eventID)
{
case 0:
// ProcessAには全てManagedCode
if(StartProcessA())
{
// ProcessBには、UnManagedCodeの内容があり、
// 終わりにGC.Collect()が必ず行われる
StartProcessB();
}
case 1:
flagA = true;
break;
case 2:
flagB = true;
break;
....
case 9:
....
break;
}

if(flagA && flagB)
{
CallAnotherMethod();
}


これもおそらくムダ。
 StartProcessA が true を返さないと StartProcessB が呼び出されないなら、CallAnotherMethod は、少なくとも StartProcessA が true を返さないと実行されない。
 だったら、こうでいい。
コード:

if (StartProcessA()) {
StartProcessB();
CallAnotherMethod();
}


 3,4,…8にどの様な処理が入るのかわからないけど、少なくとも0の処理が終わっていないと CallAnotherMethod は呼ばれないのだから、必要ないでしょ。
 さらに、EventHandleClass は ParentClass の動的な変数なんだから、flagA, flagB が初期化されるのは1回だけで、もし eventID 3〜8で false にするんだとしても、StartProcessB が終了するまでロックしているはずだから、結局実行されないんじゃない?

 んで、少なくとも eventID 0,1,2は、同じスレッドからコールされますよね?だとすると、eventID 1が lock ステートメントを抜けるときに、ロックが解除されるんじゃないかなぁ?
 別のスレッドからコールされるなら、0が終わるまで1,2はブロックされるわけで。そうすると、何時 CallAnotherMethod が呼ばれるんだろう?

 あ、eventID 1,2は別スレッドから呼ばれるのか。へぇ?どうやって?呼ぶはずのメソッドは、protected なのに。どう見ても、StartProcessA でデリゲートを設定しなきゃいけいないような気がするけど、それを、いつ、行うんだろう?

 eventID 0の case ブロックに break がないのは、ただの転記ミスなんだろうな。

 だいたい、flagA, flagB は、ProcessA, ProcessB が終了したかどうかということを示しているわけですが、プロセスが終了するまでブロックするなら、なんのためにスレッドを分ける必要があるんだろう?
 で、もし、スレッドごとに ParentClass インスタンスを作るなら、EventHandleClass インスタンスもスレッドごとに作られるので、結局ロックされるスレッドはない、、、ということになってしまうわけで。

 ホント、パズルですねぇ。。。


 「転記しなかったコードで色々制御しているんだ」ということなら、公開するコードは現象が確認できる最低限のコードにしてください。書かれていないことをあれこれ想像するのは、本当に疲れますから。

[ メッセージ編集済み 編集者: Jitta 編集日時 2006-03-23 22:19 ]
なちゃ
ぬし
会議室デビュー日: 2003/06/11
投稿数: 872
投稿日時: 2006-03-23 23:13
どーっかで EventHandleClass のインスタンスをlockしてませんかね?

引用:

Bobさんの書き込み (2006-03-23 18:12) より:
Layer-1のManagerClassが、ParentClassの生成・Start/Cancel制御も行われているので、ParentClassが先に進まないことを感知した上、Cancel処理だけは正常に終了しました。
従って、DeadLockが発生した可能性が低く、Result通知はどこかで紛失されたと疑っていますが。


っておっしゃってますが、Cancel処理を呼び出すスレッドがロックを取得していたら
同じ動作になると思いますので、DeadLockみたいな状態になっている可能性も
あるように思います。

ところで、GC.Collectを実行しない場合は正常に動くんですか?
囚人
ぬし
会議室デビュー日: 2005/08/13
投稿数: 1019
投稿日時: 2006-03-24 02:33
どうも難しいですね.
同期,非同期という言葉の使いどころがおかしい様な気がします.
以降,揚げ足を取るというつもりではないので悪しからず.私の無知故です.

引用:

ProcessAとは外部と通信するSubClassAの送信メソッドで、送信完了すれば、応答を待たない同期メソッドの感じ。


応答を待たないのであれば,非同期ではないでしょうか.

引用:

ResultOfProcessAとResultOfProcessBは非同期スレッドに同期にeventProcessで「lock」され、生きていると認識しています。


非同期なのか同期なのかよくわかりません.

引用:

従って、DeadLockが発生した可能性が低く、Result通知はどこかで紛失されたと疑っていますが。


見た感じロックしているのは一つのインスタンスなので「デッドロック」という状態は有り得ないでしょう.排他制御で用いるデッドロックという状態は「お互いがお互いの開放待ち」という状態です.今回の場合はあったとしても行列(先頭がどかない)待ちではないでしょうか.(他でロックしているのならばその限りではない)

ちなみに
コード:
lock( this )


というのは,今ではかなり悪いロックのやり方であると言われています.これでは実際にロックしたい箇所がロックできない事が大いに有り得ますからね.詳細は調べてみてください.

Layer-1 Layer-2 という表現がよくわからないのですが,何か特別な意味があるのでしょうか?
結局排他制御したいリソースは,flagA と flagB のみなのでしょうか?
false になるのは最初だけで,以降 true にしかならないのならば,排他する必要はあるのでしょうか.

単純に,case 3〜8 が非常に長い処理を行っている,という事はないでしょうか?

_________________
囚人のジレンマな日々
Bob
常連さん
会議室デビュー日: 2006/03/23
投稿数: 31
投稿日時: 2006-03-24 09:28
ご回答ありがとうございます。

引用:

Jittaさんの書き込み (2006-03-23 22:09) より:
> // ProcessBには、UnManagedCodeの内容があり、
> // 終わりにGC.Collect()が必ず行われる
ムダ。

 GC.Collect は、マネージドメモリのガベージをコレクトします。マネージされていないものをコレクトするわけではありません。
(っつうか、マネージしているからコレクトできるわけで、マネージしていないものをどうやって知るのさ?)
 例えば、エクセルを COM 連携で起動したとき、Quit しないとエクセルプロセスは残ります。ガベージコレクトを行っても、「プロセス終了」を行わないと、プロセスが終了しないというリークが発生することになります。


ProcessBのライブラリにはManagedとUnmanagedコード両方存在するので、GC.Collect()の意図はメモリリークを防ぐためで、詳細は不明です。ただ、この中のGC.CollectはC#側に影響がないかと。。。

引用:

これもおそらくムダ。
 StartProcessA が true を返さないと StartProcessB が呼び出されないなら、CallAnotherMethod は、少なくとも StartProcessA が true を返さないと実行されない。
 だったら、こうでいい。
コード:
if (StartProcessA()) {
	StartProcessB();
	CallAnotherMethod();
}


 3,4,…8にどの様な処理が入るのかわからないけど、少なくとも0の処理が終わっていないと CallAnotherMethod は呼ばれないのだから、必要ないでしょ。



まず、ProcessAの結果は非同期でしか分からないので、ここのif(StartProcessA())はあくまで相手に通知できたことだけなので、相手は受け取ったかの判断にはなりません。
あと、ProcessAの結果を受けてから、ProcessBに進むやり方もあるが、ProcessAの結果を待ってる間に、ProcessBを先に行う経緯もあります。

引用:

 さらに、EventHandleClass は ParentClass の動的な変数なんだから、flagA, flagB が初期化されるのは1回だけで、もし eventID 3〜8で false にするんだとしても、StartProcessB が終了するまでロックしているはずだから、結局実行されないんじゃない?

 んで、少なくとも eventID 0,1,2は、同じスレッドからコールされますよね?だとすると、eventID 1が lock ステートメントを抜けるときに、ロックが解除されるんじゃないかなぁ?

 別のスレッドからコールされるなら、0が終わるまで1,2はブロックされるわけで。そうすると、何時 CallAnotherMethod が呼ばれるんだろう?

 あ、eventID 1,2は別スレッドから呼ばれるのか。へぇ?どうやって?呼ぶはずのメソッドは、protected なのに。どう見ても、StartProcessA でデリゲートを設定しなきゃいけいないような気がするけど、それを、いつ、行うんだろう?



ここで、0、1、2とも異なるスレッドからコールされるので、デバッガでみる限りいつもロックがかかってます。仰るとおり、非同期で呼ばれるprotectedメソッドは全て各クラスのオブジェクト生成時の初期化処理で行っています。

引用:

 eventID 0の case ブロックに break がないのは、ただの転記ミスなんだろうな。

 だいたい、flagA, flagB は、ProcessA, ProcessB が終了したかどうかということを示しているわけですが、プロセスが終了するまでブロックするなら、なんのためにスレッドを分ける必要があるんだろう?
 で、もし、スレッドごとに ParentClass インスタンスを作るなら、EventHandleClass インスタンスもスレッドごとに作られるので、結局ロックされるスレッドはない、、、ということになってしまうわけで。

 ホント、パズルですねぇ。。。

 「転記しなかったコードで色々制御しているんだ」ということなら、公開するコードは現象が確認できる最低限のコードにしてください。書かれていないことをあれこれ想像するのは、本当に疲れますから。



すみません。転記コードにミスがあります。EventHandleClassはParentClassの状態遷移を担う役目なので、関係は1対1です。

「歴史」のある構成で、改善すべき点が多いが、本当に申し訳ありません。
Bob
常連さん
会議室デビュー日: 2006/03/23
投稿数: 31
投稿日時: 2006-03-24 09:59
引用:

ちなみに
コード:
lock( this )


というのは,今ではかなり悪いロックのやり方であると言われています.これでは実際にロックしたい箇所がロックできない事が大いに有り得ますからね.詳細は調べてみてください.


ご指摘ありがとうございます。ここに自分も気になりますが、これから調べてみます。どなたは何かの関連情報をお持ちでしたら、ご教授お願い致します。
Jitta
ぬし
会議室デビュー日: 2002/07/05
投稿数: 6267
お住まい・勤務地: 兵庫県・海手
投稿日時: 2006-03-24 21:36
 まず訂正。
 2006-03-23 22:09 に『eventID 1が lock ステートメントを抜けるときに、ロックが解除されるんじゃないかなぁ?』と書きましたが、これは誤りでした。Monitor.Enter を呼び出したのと同じ回数 Monitor.Exit を呼び出さなければ、ブロックは解除されません。


 掲示板を見ている人は、あなたの会社にいて、あなたと同じプロジェクトを担当していて、あなたと同じソースコードを見ているわけではありません。また、あなたの事情などもわかりません。この点を、しっかりと記憶しておいてください。
 その上で、「解決したい問題」と、問題を解決するために必要な「情報」を提示する必要があります。

 今回問題になるのは、

1.GC.Collect によって回収されるもの
2.処理の実行順序
3.ロックの範囲
4.順次処理したい処理

だと思います。
 コーディングの意図と、実際にコーディングされた結果にズレがあると、「バグ」として検出されます。検出されたバグを潰すためには、コーディングの意図、すなわち設計と、実際のコードにどの様な差異があるかを見つけなければなりません。


1.GC.collect によって回収されるもの
引用:

GC.Collect()の意図はメモリリークを防ぐためで、詳細は不明です。


 GC.Collect をしたからといって、メモリリークが防げるわけではありません。Collect を明示的に指示することによって回収可能なメモリは、明示的に指示しなくてもいずれ回収されます。つまり、明示的に呼び出す必要はありません。
 GC.Collect をコールしても、メモリリークは発生し得ます。例えば、Win32API を使って、グローバルヒープからメモリ確保を行った場合、明示的に GlobalFree を実行しなければ、リークします。これは GC が管理するものではないため、GC.Collect では回収されません。

 通信をするために、ポートを開くと思います。このポートもアンマネージドリソースです。明示的に閉じなければ、排他がかかったままになります。
 もっとも、フレームワークで用意されているものは、ファイナライザで Dispose するように実装されていることが期待できますから、GC.collect でマネージドメモリがファイナライズされるときに閉じられるでしょう。
 ただし、コレクトされるのは参照が切れているもののみですから、参照がつながっている場合はコレクトされません。それどころか、ジェネレーションが上がり、自動実行時に回収されなくなるかもしれません。

 これらのことより、GC.Collect は「アンマネージコードを使っている部分のメモリリークを防ぎたい」という設計意図を満足しないため、「バグ」であると判断します。これは GC.Collect がバグなのではなく、GC.Collect でメモリリークがなくなるという設計思想がバグである、という意味です。

 GC.Collect を実行すると、保持しているメモリから参照できないメモリが回収されます。
コード:
void caller() {
    executeThread();
    GC.Collect();
}

void executeThread() {
    Thread t = new Thread(someMethod());
    t.Start();
}


このとき、t は参照が切れているため、回収対象になると思います。someMethod の実行がどうなるのか、検証が必要です。
 まず、このようなコードで、実行中であるにもかかわらず、回収されて消えてしまうのか検証することで、懸案事項のひとつは解消すると思います。

 実は、Thread が IDisposable でないことが不思議です。スレッドハンドルをどこかで閉じてやる必要があると思うのですが。Finalize がオーバーライドされているから、その中でやっているのかな?


2.処理の実行順序
 この情報は提示されていません。
 意図通りに処理が実行されているのか、確認する必要があります。これは単純に、時間とスレッド ID、通過ポイントを出力して確認するということでいいでしょう。


3.ロックの範囲
 この情報は、実際のコードのみ提供されています。設計と実際のコードが一致しているかどうか、検証してください。
 また、ロックの範囲は「短く」「少なく」実装することが望ましいです。本当にロックしなければならないところだけがロックされているか、確認してください。この意味で、現在のコードは「長く」「多く(ソース上は一箇所だが、処理の流れを一本の線で表すと、多数になる)」実装されているため、見直しが必要ではないかと考えます。


4.順次処理したい処理
 この情報は提示されていません。
 ProcessA と ProcessB が終了していれば、AnotherMethod を実行するということはわかりました。しかし、ProcessB だけが実行されることもある、とのことです。この辺りのメソッドが実行されるユースケース、および条件が整理されていないように感じます。少なくとも、私は読み取れませんでした。

スキルアップ/キャリアアップ(JOB@IT)