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

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

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

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

Q&A

解決済

3回答

8813閲覧

C# task Mutexでの排他制御 逆に実行結果が遅くなってしまう

tetsu123

総合スコア26

C#

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

0グッド

1クリップ

投稿2017/02/25 03:59

【行いたいこと】
既存の処理(文字列の配列から1つデータを取り出して○○する*1処理6秒位です)
を並列で処理して時間の短縮を図りたいと考えています。
配列から1つデータを取り出す際には排他処理を行って取り出すデータにダブりが無いようにしたいです。
*因みにこの配列自体が20万近くデータがありますのでそこそこ長い時間処理を続けることになります

【行ったこと】
・並列処理はtaskを使用すれば良いということでTask.Factory.StartNewで取り敢えず10個タスクを作って実行
・排他処理はMutexを使用して多重起動を禁止すれば良いということで配列のカウントアップする処理をMutexのWaitOneで排他処理にした

【疑問】
Testで配列から取り出した文字列をConsole.Writeで書き出すテストを「taskを使用しない方法」と「10個taskを作って並列で処理する方法」で試した結果、何故か後者の方が遅い結果となりました。
何故並列処理を行って遅い結果となるのか原因が知りたいです。
*ThreadIDを出して確認しましたが、複数のtaskは走っているようです。そうなるとMutexの使い方が間違っているのかなと考えたのですが、行き詰まってしまったので質問させて頂きました。宜しくお願いいたします。

【コード】
//文字列の配列クラス
class StringArray
{
private string[] arystring;
private string strTemporary;
private int intI;
private Mutex objMutex = new Mutex();

public StringArray(string[] aarystring) { this.arystring = aarystring; intI = 0; } //呼び出されると文字列データを返す() public string GetString() { //配列の限界に達したら空文字を返す if (intI >= this.arystring.Length) { return "空文字__" + intI; } //配列のカウントアップの重複を避けたいのでここから多重起動を禁止する objMutex.WaitOne(); strTemporary = this.arystring[intI]; intI = intI + 1; objMutex.ReleaseMutex(); return strTemporary + "__" + intI.ToString(); }

}

private void button3_Click(object sender, EventArgs e)
{
int intI;
int intJ;
int intK;

//何らかの文字列配列を用意*今回は0~2000までの数字を文字列として渡すことにします string[] arystring; ArrayList aryTemporary = new ArrayList(); for (intI = 0; intI < 2000; intI++) { aryTemporary.Add(intI.ToString()); } arystring = (string[])aryTemporary.ToArray(typeof(string)); //文字列配列のクラスを定義する StringArray classStringArray = new StringArray(arystring); //【比較テスト】 //○並列処理ではない場合 /* var watch0 = Stopwatch.StartNew(); for (intJ = 0; intJ < arystring.Length; intJ++) { Console.Write("0__結果:{0}__経過時間:{1}" + Environment.NewLine, classStringArray.GetString(), watch0.ElapsedMilliseconds); } */ //○並列処理の場合 var watch = Stopwatch.StartNew(); for (intK = 0; intK < 10; intK++) { Task.Factory.StartNew(() => { for (intJ = 0; intJ < arystring.Length; intJ++) { Console.Write("1__実行数:{0}__経過時間:{1}" + Environment.NewLine, classStringArray.GetString(), watch.ElapsedMilliseconds); } }); } return;

}

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

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

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

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

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

guest

回答3

0

すでに解決済みでしたか・・・。
せっかく書いたので投稿しておきます。参考まで。

  • まず、「何故並列処理を行って遅い結果となるのか」という問いに対しては、他の方も回答されているとおり「タスクを10個に分けても、結局1か所で待たされているから」というのが答えになります。

  • ご提示の検証用コードですが、「文字列の配列から1つデータを取り出して○○する」の部分が6秒かかるほど重いのであれば、そこを再現するためにThread.Sleepを入れておくとかしないと、このような性能検証は的外れな結論になる危険があります。

(排他処理をすること自体にそれなりの負荷があるので、GetStringの処理がシンプルすぎると「並列にしないほうが速い」という結論になってしまう)

  • なお、カウントアップの排他にMutexを使われていますが、Mutexはプロセス間での排他が必要な場合に使うのが一般的かなと思います。

今回のような例ではlockを使うか、カウントアップ変数にInterlockedを使います。

以上をふまえ、Interlockedを使って、必要なところ以外を排他しないように書き直してみた例が以下になります。
(ついでに気になる点にコメントを入れさせていただきました)

C#

1//このクラスではカウントを管理しない 2//(聞かれた位置の値を処理して返すだけ) 3class StringArray 4{ 5 private string[] arystring; 6 7 public StringArray(string[] aarystring) 8 { 9 this.arystring = aarystring; 10 } 11 12 //引数でインデクスを指定してもらう 13 public string GetString(int i) 14 { 15 //時間がかかる処理なのでSleepしておく 16 Thread.Sleep(10); 17 18 if (i >= this.arystring.Length) 19 { 20 return "空文字__" + i; 21 } 22 23 return this.arystring[i] + "__" + i; 24 } 25}

C#

1private void button3_Click(object sender, EventArgs e) 2{ 3 //※↓こういうふうに一時変数まで全部宣言しちゃうようなことは 4 // C#では普通やらないです。意味がないので。 5 // 変数は使う時に宣言します。 6 //int intI; 7 //int intJ; 8 //int intK; 9 10 //※この用途なら普通はList<T>を使います 11 //(ArrayListは過去の遺産なので基本使わない) 12 var list = new List<string>(); 13 for (int i = 0; i < 2000; i++) 14 { 15 list.Add(i.ToString()); 16 } 17 var aryString = list.ToArray(); 18 19 //文字列配列のクラスを定義する 20 StringArray classStringArray = new StringArray(aryString); 21 22 //【比較テスト】 23 //○並列処理ではない場合 24 var watch0 = Stopwatch.StartNew(); 25 for (int i = 0; i < aryString.Length; i++) 26 { 27 Console.WriteLine(classStringArray.GetString(i)); 28 } 29 //手元の環境では20秒くらい 30 Console.WriteLine("0結果:{0}ms", watch0.ElapsedMilliseconds); 31 32 //○並列処理の場合 33 var watch1 = Stopwatch.StartNew(); 34 int idx = -1; 35 var tasks = new Task[10]; 36 for (int iTask = 0; iTask < tasks.Length; iTask++) 37 { 38 tasks[iTask] = Task.Factory.StartNew(() => 39 { 40 while (true) 41 { 42 //Interlocked.Incrementでカウントアップしつつ値を取る 43 var next = Interlocked.Increment(ref idx); 44 if (aryString.Length <= next) 45 { 46 break; 47 } 48 Console.WriteLine(classStringArray.GetString(next)); 49 } 50 }); 51 } 52 Task.WaitAll(tasks); 53 //手元の環境では4秒くらい 54 Console.WriteLine("1結果:{0}ms", watch1.ElapsedMilliseconds); 55}

投稿2017/02/25 08:14

oika

総合スコア425

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

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

0

以下の記事の図「シングルCPUとマルチCPUにおけるマルチスレッドの比較」を見てください。

第1回 マルチスレッドはこんなときに使う (1/2)
http://www.atmarkit.co.jp/ait/articles/0503/12/news025.html

たぶん、その図の ② のようになっていて、その図の下に書いてあるように "実際にはすべての処理が終了するまでの時間は変わらず、むしろ処理切り替えのために悪化する可能性もある" ということになっているのであろうと思われます。

投稿2017/02/25 04:20

退会済みユーザー

退会済みユーザー

総合スコア0

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

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

tetsu123

2017/02/25 07:49

まだ知識が浅いのでとても参考になります。 恐らく私の行いたい処理自体はマルチスレッドに適していると思うのでこのまま進めたいと思います。 しかしこれを見るとマルチCPUの優秀さがよく分かりますね。処理を実行するPCはもっと贅沢させた方が良いかもしれない・・・ 回答ありがとうございました。
guest

0

ベストアンサー

同時に10個のタスクが走っていても、配列にアクセスできるのはそのうちの一つだけで、他はアクセス待ちになります。待っている間はタスクは停止しますから、結果的には遅くなりますよ。

この場合10個のタスクでそれぞれ配列の一部分のみを担当するようにして(つまり20万要素ある配列なら、2万要素×10と考える)やる方が要望に沿うでしょう。

投稿2017/02/25 04:09

tacsheaven

総合スコア13703

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

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

tetsu123

2017/02/25 07:49

あー、そういうことでしたか。同時にアクセス出来るのが1つだけでは全く意味がないですね・・・ 配列を分割する方法を検討してみたいと思います。 助かりました。回答ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問