スレッドとタスク

スレッド


  • 同時実行可能なスレッド数はプロセッサ数に依存する。

  • .netではスレッドひとつに1MBのサイズが必要。

  • スレッドの切り替えは高コスト。(コンテキストスイッチが発生する)

  • 大量のスレッド作成はかえって逆効果。スレッドを作成するのではなく、タスクを生成したほうがよい。

タスク


  • タスクはスレッドに割り当てられ実行される。このあたりの一連の管理(スレッド作成、タスク割当て)はスレッドプールで行なわれる。

  • タスクの切り替えにはコンテキストスイッチが発生しないので、低コスト。

  • スレッドプールは効率よく処理が行なえるようにスレッドの生成、タスクの割り当てを行なうしくみ。

動作確認コード

  public class SomethingProcesser
  {
    public int Result { get; set; }

    public List<SomethingProcesser> Children { get; set; }

    public SomethingProcesser()
    {
      Children = new List<SomethingProcesser>();
      Result = 0;
    }

    public void Proc()
    {
      _proc(this);
    }

    private void _proc(SomethingProcesser proc)
    {
      foreach (var child in proc.Children)
      {
        _proc(child);
        child.Proc();
      }

      Thread.SpinWait(10000);
    }
  }

  public interface IProcesser
  {
    void DoProc();
  }

  public class SomethingTask : IProcesser
  {
    private SomethingProcesser processer;

    public SomethingTask(SomethingProcesser processer)
    {
      this.processer = processer;
    }

    public void DoProc()
    {
      _doProc(0);
    }

    private void _doProc(int procIdx)
    {
      if (processer.Children.Count > procIdx)
      {
        Task task = new Task(() => {
          var p = processer.Children[procIdx];
          _doProc(++procIdx);
          p.Proc();
        });
        task.Start();
        task.Wait();
      }
    }
  }

  public class SomethingThread : IProcesser
  {
    private SomethingProcesser processer;

    public SomethingThread(SomethingProcesser processer)
    {
      this.processer = processer;
    }

    public void DoProc()
    {
      _doProc(0);
    }

    private void _doProc(int procIdx)
    {
      if (processer.Children.Count > procIdx)
      {
        Thread task = new Thread(() => {
          var p = processer.Children[procIdx];
          _doProc(++procIdx);
          p.Proc();
        });
        task.Start();
        task.Join();
      }
    }
  }
public partial class Form1 : Form
{
  public Form1()
  {
    InitializeComponent();
  }

  private SomethingProcesser processer;

  private void Form1_Load(object sender, EventArgs e)
  {
    processer = new SomethingProcesser();
    for (int i = 0; i < 100; i++)
    {
      var child = new SomethingProcesser();
      processer.Children.Add(child);
      for (int j = 0; j < 100; j++)
      {
        child.Children.Add(new SomethingProcesser());
      }
    }
  }

  private void button1_Click(object sender, EventArgs e)
  {
    var sw = new Stopwatch();
    sw.Start();
    processer.Proc();
    sw.Stop();
    Console.WriteLine("直列実行:{0}ms", sw.ElapsedMilliseconds);
  }

  private void button2_Click(object sender, EventArgs e)
  {
    //スレッド
    var sw = new Stopwatch();
    var thread = new SomethingThread(processer);
    sw.Start();
    thread.DoProc();
    sw.Stop();
    Console.WriteLine("スレッド使用:{0}ms", sw.ElapsedMilliseconds);
  }

  private void button3_Click(object sender, EventArgs e)
  {
    //タスク
    var sw = new Stopwatch();
    var task = new SomethingTask(processer);
    sw.Start();
    task.DoProc();
    sw.Stop();
    Console.WriteLine("タスク使用:{0}ms", sw.ElapsedMilliseconds);
  }
}

実行結果

何度か実行したが、速度的には
1,タスク 2,スレッド 3,直列実行
の順番だった
メモリ使用率はどの方法も変わらず。

処理時間例)
直列実行:1782ms
タスク使用:917ms
スレッド使用:1397ms