【C#】マルチスレッド処理に関して

03_マルチスレッド処理に関して

概要

  • マルチスレッド処理に関するまとめを行う。

マルチスレッド処理

  • マルチプロセスとは別物なので注意!
    • プロセス -> exeファイル
      • スレッド -> プロセス内の処理

並列稼働と並行稼働

  • 並列稼働と並行稼働は違うらしい。
    • 並列稼働: 複数のCPUで同時に処理を行う。
    • 並行稼働: 一つのCPUが複数の処理を切り替えながら動作し、疑似的に複数のCPUがあるように処理を行う。
マルチスレッドの動作原理
マルチスレッドの動作原理

マルチスレッド処理の目的

1. レスポンスタイムの向上

  • 重い処理が行われている間に、他の処理を行わせることで、レスポンスタイムが向上する。
  • 複数のクライアントに対して同時に処理を行うことができる。

2. スループット(単位時間当たりの処理性能)の向上

  • ディスク上にあるすべてのファイルをリストアップするような処理を行うとき、
    ディスクにアクセスしている時間は非常に長くなるが、その間CPUでの処理はほとんどできない。

    • このようなCPUの空き時間がパフォーマンス向上の余地になる。

マルチスレッド処理の実現方法

 
方法 メリット デメリット
スレッド(Thread) ・ 各スレッドに優先順位を設定できる

・ スレッドの一時停止/再開/中断を行うことができる

・ スレッドの作成と破棄を繰り返すとパフォーマンスが落ちる

・ メソッドにパラメータを設定できない

・ メソッドの戻り値を得るのが困難

スレッドプール(ThreadPool) ・ 効率よく複数のスレッドを実行できる

・ object型のパラメータを1つだけ設定できる

・ パラメータがobject型1つのみ

・ メソッドの戻り値を得るのが困難

・ 優先順位付けや待機、停止など、スレッドの細かな制御が難しい

・同時に実行できるスレッドの数が制限されている

デリゲート(BeginInvoke) ・ メソッドに型のあるパラメータを指定できる

・ 簡単に戻り値を得ることができる

・ 優先順位付けや待機、停止など、スレッドの細かな制御が難しい

・ 同時に実行できるスレッドの数が制限されている

タイマー(Timer) ・一定時間間隔でメソッドを実行することができる ・スレッドプールがいっぱいだとうまく動作しない

スレッド

  • Threadクラスで指定したメソッドを別スレッドで起動(実行)する仕組みを提供する。
  • スレッドの生成にはそれなりのリソースが消費されえるため大量にスレッドが処理される場合、パフォーマンスが低下する。

サンプルコード

using System;
using System.Threading;

public class List1_1
{
    public static void Main()
    {
        Thread threadA = new Thread(
                new ThreadStart(ThreadMethod));// (1)

        threadA.Start();// (2)

        for(int i = 0; i < 100; i++)
        {
            Thread.Sleep(5);
            Console.Write("B");
        }
    }

    //別スレッドで動作させるメソッド
    private static void ThreadMethod()
    {
        for(int i = 0; i < 100; i++)
        {
            Thread.Sleep(5);
            Console.Write("A");
        }
    }
}
  • 実行結果
ABBABAABBABAABBAABBABAABABBABAABABBAABABABABABABBABABABABABABABAABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABAB
解説
  • (1): Threadクラスのインスタンスを生成する。
    • ThreadStartクラスのコンストラクタに別スレッドで動作させたいメソッド名を指定する。
        Thread threadA = new Thread(
                new ThreadStart(ThreadMethod));// (1)
  • (2): 別スレッドによる処理を実行する。
    • ThreadオブジェクトのStartメソッドを呼び出す。

スレッドプール

  • ThreadPoolクラスで提供される。
  • キューに入れられたリクエスト(処理)をスレッドプールが用意しているスレッドにより次々に実行していく仕組み。
  • 一度確保したスレッドのリソースをできる限り再利用するように設計されている。
    • スレッドで問題になっていたパフォーマンス低下が解消される。

サンプルコード

using System;
using System.Threading;

public class List2 {
    public static void Main() {
        // ThreadMethodをスレッドプールで実行できるように
        // WaitCallbackデリゲートを作成
        WaitCallback waitCallback = new WaitCallback(ThreadMethod);// (1)

        // スレッドプールに登録
        ThreadPool.QueueUserWorkItem(waitCallback,"A");// (2)
        ThreadPool.QueueUserWorkItem(waitCallback,"B");// (3)

        Console.ReadLine();
    }

    private static void ThreadMethod(object state) {
        for(int i = 0; i < 100; i++){
            Thread.Sleep(5);
            Console.Write("{0}",state);

        }
    }
}
  • 実行結果
ABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABAB
解説
  • (1): スレッドプールで使用できるようにデリゲートを作成。
    • 処理を行いたいメソッドを引数としてWaitCallbackオブジェクトを作成する。
WaitCallback waitCallback = new WaitaCallback(ThreadMethod);
  • (2): スレッドを開始する。
    • ThreadPool.QueueUserWorkItemメソッドを使用する。
      • 引数は、第一引数にデリゲート、第二引数にobject型のパラメータを指定すること。

デリゲート

  • デリゲートに関するまとめはこちら
  • デリゲート=メソッドの呼び出しをラッピングしたもの
  • デリゲートを通じて間接的に呼び出すことができる。
  • デリゲートに登録されたメソッドを実行するには、普通に実行するInvokeメソッドのほかに、
    別スレッドでメソッドを実行するBeginInvokeメソッドが使用できる。

    • デリゲートは上述したスレッドプールを利用して、登録されたメソッドを実行するという仕組みになっている。

サンプル

using System;
using System.Threading;

public class List3
{
    // 戻り値とパラメータのあるデリゲート
    delegate DateTime ThreadMethodDelegate(string c);// (1)
    static ThreadMethodDelegate threadMethodDelegate;// (2)

    public static void Main(){
        threadMethodDelegate = new ThreadMethodDelegate(ThreadMethod); // (3)

        // デリゲートによるスレッド処理呼び出し
        threadMethodDelegate.BeginInvoke(".",new AsyncCallback(MyCallback), DateTime.Now); // (4)

        Console.ReadLine();
    }

    private static DateTime ThreadMethod(string c){
        // 10ミリ秒ごとに100回cを出力
        for (int i=0; i<100;i++){
            Thread.Sleep(10);
            Console.Write(c);
        }
        return DateTime.Now;
    }

    // スレッド処理終了後に呼び出されるコールバック・メソッド
    private static void MyCallback(IAsyncResult ar)// (5)
    {
        DateTime result = threadMethodDelegate.EndInvoke(ar);// (6)
        DateTime beginTime = (DateTime)ar.AsyncState; // (7)

        Console.WriteLine();
        Console.WriteLine("{0}に処理を開始し、{1}に処理を完了しました。", beginTime,result);
    }
}
解説
  • ThreadMethodという名前のメソッドを、デリゲートを使用して別スレッドに実行し
    ThreadMethodメソッドの処理が終了した時点でMyCallbackメソッドが自動的にコールバックされるように設定している。
  • (1)
    • 別スレッドとして処理したメソッドをデリゲート宣言する。
      • delegate DateTime ThreadMethodDelegate(string c);// (1)
      • このときのパラメータの型と個数、戻り値の型は、別スレッドとして処理したメソッドに合わせておく。
        • ThreadMethod自体は、何も特別な部分のない通常のメソッドである。
  • (2),(3)
    • ここで宣言したデリゲートを実際に利用するために、
      デリゲートのインスタンスを作成しフィールド変数threadMethodDelegateとして保持する。

      • static ThreadMethodDelegate threadMethodDelegate;// (2)
      • threadMethodDelegate = new ThreadMethodDelegate(ThreadMethod); // (3)
  • (5)
    • コールバック・メソッドを作成する。
      • コールバック・メソッド: 別スレッドによる処理が終了したことをトリガーとして、自動的に呼び出されるメソッドのことである。
    private static void MyCallback(IAsyncResult ar)// (5)
    {
    }
  • (4)
    • デリゲートのBeginInvokeメソッドを呼び出すことによって、別スレッドの処理を開始する。
      • BeginInvokeメソッドの引数は下記の通り
        • デリゲートを通じて呼び出すメソッドへのパラメータ(複数ある場合は複数指定する。)
        • AsyncCallbackデリゲートでラッピングしたコールバック・メソッド
          • ステート
        • AsyncCallbackデリゲートは、コールバックしたいメソッドをコンストラクタのパラメータに指定して、そのインスタンスを作成する。
        • 最後の引数であるステートjは、object型のオブジェクトを自由に指定できる。
        • 指定したオブジェクトはコールバック・メソッドのパラメータやBeginInvokeメソッドの戻り値であるIAsyncResultオブジェクト内に保持されている。
      • 詳細は「Control.BeginInvoke Method (System.Windows.Forms)」
        // デリゲートによるスレッド処理呼び出し
        threadMethodDelegate.BeginInvoke(".",new AsyncCallback(MyCallback), DateTime.Now); // (4)

タイマー

  • Timerクラスを利用して実現する。

サンプル

using System;
using System.Threading;

public class List4 {
    private static int count = 0;
    public static void Main(){
        // 一定間隔ごとに呼び出すメソッドをTimerCallbackとして登録
        TimerCallback timerCallback = new TimerCallback(ThreadMethod);// (1)

        Console.WriteLine("くるくるまわるよ!");
        // 1秒待ってから、0.1秒ごとにtimerCallbackメソッドを呼び出す
        Timer timer = new Timer(timerCallback, null, 1*1000, 1*100);// (2)

        Console.ReadLine();
    }

    private static void ThreadMethod(object state){ // (3)
        count++;
        string output = "";
        switch(count%7){
            case 0:
                output = "  ( ・ω)";
                count = 0;
                    break;
            case 1:
                output = "  ( ・ )";
                    break;
            case 2:
                output = "  (    )";
                    break;
            case 3:
                output = "  (・  )";
                    break;
            case 4:
                output = "  (・  )";
                    break;
            case 5:
                output = "  (ω・ )";
                    break;
            case 6:
                output = "  (・ω・)";
                break;
        }
        // 行を削除
        Console.Write("\r");
        Console.Write(output);
    }
}
  • 実行結果
List4
List4

参考サイト

連載.NETマルチスレッド・プログラミング入門

カテゴリーC#

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です