質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Q&A

解決済

1回答

3741閲覧

Dictionaryへのアクセスがデッドロックになる

退会済みユーザー

退会済みユーザー

総合スコア0

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

0グッド

0クリップ

投稿2017/01/26 04:21

お世話になっております。

非同期処理でList<Dictionary<ParamType,double>>にParamTypeごとの計算処理を行いたいと思っています。

C#

1 var adjust_params = Enumerable.Range(0, node_depth).Select(index => 2 new Dictionary<SamplingKey, double>() { 3 {ParamType.a,0}, 4 {ParamType.b,0}, 5 {ParamType.c,0}, 6 7 ).ToArray(); 8 9 Parallel.ForEach(nodes.Values, node => 10 { 11 int gen_counts = node.tables.AsParallel().Sum(data => data.generate) 12 13 var ab_task = Task.Run(() => 14 { 15 double y1 = 0; 16 double y2 = 0; 17 for (int i = 1; i < gen_counts; i++) 18 { 19 double y = generateOrNot(); 20 y1 += 1 - y; 21 y2 += y; 22 } 23 return new { a = y1, b = y2 }; 24 }); 25 26 double c = Math.Log(node.price); 27 28 lock(adjust_params[node.depth]){ 29 adjust_params[node.depth][ParamType.a] += ab_task.Result.a; 30 adjust_params[node.depth][ParamType.b] += ab_task.Result.b; 31 adjust_params[node.depth][ParamType.b] += c; 32 } 33 });

この処理ではnodeの数が多すぎるとlock(adjust_params[node.depth])でデッドロックになります。恐らく、二つ以上のスレッドが運悪くクリティカルセクションに侵入してしまうとadjust_paramの読み込みがロックされてデッドロック状態になってしまうようです。nodeがそこまで多くなければ問題なかったのですが、CPU使用率が100%になったりするので、遅延発生で同時にクリティカルセクションに侵入してしまうnodeがあるのかと思っています。やってることは重たい処理を並列化して、nodeごとの合計を出したいだけなのですが、どのように改善するのがよいでしょうか。

アドバイスいただける方いましたらよろしくお願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

この手の計算処理は、generateOrNotが相当時間かからない限りパラレルにしたりTask.Runした方がむしろ遅そうではありますが…

とりあえず以下のように書き直した場合ロックされますか?

C#

1Task.Run(() => 2{ 3 double y1 = 0; 4 double y2 = 0; 5 for (int i = 1; i < gen_counts; i++) 6 { 7 double y = generateOrNot(); 8 y1 += 1 - y; 9 y2 += y; 10 } 11 return new { a = y1, b = y2 }; 12}).ContinueWith(x => 13{ 14 // ここでa, bを外に出す必要性は無いはずだが、こちらの方が分かりやすいし安全なので。 15 double a = x.Result.a; 16 double b = x.Result.b; 17 double c = Math.Log(node.price); 18 lock (adjust_params[node.depth]) 19 { 20 adjust_params[node.depth][ParamType.a] += a; 21 adjust_params[node.depth][ParamType.b] += b; 22 adjust_params[node.depth][ParamType.b] += c; 23 } 24});

投稿2017/01/26 04:33

haru666

総合スコア1591

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/01/26 06:10 編集

こちらサンプルは実際のコードを書き直してかなり簡略化していますが、実際は内部でもっと重たいタスクが走っています。generateOrNodeはものすごく重たい処理と考えて差し支えないです。とりあえずやってみますね。 追記 一応計算回るようになりました!ありがとうございます!ただ、理屈があまり整理できていないのですが、同期処理になるのでContinueWithはThreadごとには排他制御になるとは思うのですが、それでもうまくいくのであれば並行スレッドじゃなくてメインスレッドに投げるからですかね?(同期スレッドで実行されるならlock)も消せる気がしました。ありがとうございます。
haru666

2017/01/26 06:03

ふと思ったのですが、adjust_paramの値の取得処理が途中でなければDictionaryではなくクラスにして class ParamValue { public double a; public double b; public double c; } とすれば、ロック不要でアトミックな操作が直接できたりしませんか?
退会済みユーザー

退会済みユーザー

2017/01/26 06:41 編集

あぁ確かにそうですね...。なんか頭でっかちになってました。ありがとうございます。 haru666さんのコードがadjust_paramをロックしてたのは外での書き出し読み出しを考慮してるからですよね? ありがとうございます。 追記  ContinueWithは同じスレッド上でタスク後の処理を継続するようなので、ほかのスレッドからも書き込みがあるので、やっぱロックも入れないとだめなきがしました。上手くいってるのは、いったん同じスレッドで同期をすることで、ほかスレッド上でロック前の同期待ちが発生する確率が小さくなるからな気がしています。 また、思い出したのですが、dictionaryにしたのはadjust_paramsの要素は本来は構造体でロックの時にlistのインデックスごとにロックするのができなくてDictionaryにした経緯があったと思います。classにすれば同じようにロックできると思いますが、ロック省くのは無理そうなきがしました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問