マルチスレッドプログラミングは難しい

Last Modified: Wed Apr 7 10:54:26 UTC 2010

C# を使って、複数スレッドで協調動作する簡単なメッセージキューの 作成がいかに苦労するかというのをお見せします。(約62分)


まとめ:

  1. キューのみ → エラーになる。
  2. キュー + spin lock (ポーリング) → 動くけどCPUの無駄。
  3. キュー + ウェイト×1 → 一度に複数回 送られたらダメ。
  4. キュー + ウェイト×2 (ぎっこんばったん方式) → queue じゃない。
  5. キュー + ウェイト + 条件判定 → 受け取り側が複数個あるとダメ。
  6. キュー + Semaphore → ようやく完成。
  7. マルチスレッドプログラミングは世界を滅ぼす。

参考資料:


foo.cs (最終形)

(注意: ここでは読みやすさのため、排他制御の lock { } 節をつけていません。 実際には Queue にアクセスする箇所はすべて lock { } を使って保護する必要があります。)

// foo.cs

using System;
using System.Threading;
using System.Collections.Generic;

class MySemaphore
{
  class Customer
  {
    private int id;
    private AutoResetEvent trigger;

    public Customer(int id)
    {
      this.id = id;
      this.trigger = new AutoResetEvent(false);
    }

    public void Sleep()
    {
      Console.WriteLine("customer "+id+" sleeping...");
      trigger.WaitOne();
    }

    public void WakeUp()
    {
      Console.WriteLine("customer "+id+" wake up!");
      trigger.Set();
    }

  }

  private Queue<Customer> waitq;
  private int zaiko;
  private int customerid;

  public MySemaphore()
  {
    waitq = new Queue<Customer>();
    zaiko = 0;
    customerid = 0;
  }

  public void Release()
  {
    zaiko++;
    if (0 < waitq.Count) {
      Console.WriteLine("wakeup the customer");
      Customer customer = waitq.Dequeue();
      customer.WakeUp();
    }
  }

  public void WaitOne()
  {
    if (0 == zaiko) {
      Console.WriteLine("zaiko nashi.");
      Console.WriteLine("customer put in the queue");
      Customer customer = new Customer(customerid++);
      waitq.Enqueue(customer);
      customer.Sleep();
    }
    zaiko--;
  }
}

class Foo 
{
  private const int N = 10;
  private static Queue<int> queue;	// shared
  private static MySemaphore trigger1;	// shared

  private static void Main(string[] args)
  {
    queue = new Queue<int>();
    trigger1 = new MySemaphore();
    new Thread(new ThreadStart(Producer)).Start();
    new Thread(new ThreadStart(Producer)).Start();
    new Thread(new ThreadStart(Consumer)).Start();
    new Thread(new ThreadStart(Consumer)).Start();
  }

  private static void Producer()
  {
    Console.WriteLine("producer");
    for (int i = 0; i < N; i++) {
      int x = i;		// product
      queue.Enqueue(x);
      trigger1.Release();
      Console.WriteLine("sent:"+x);
    }
  }

  private static void Consumer()
  {
    Console.WriteLine("consumer");
    for (int i = 0; i < N; i++) {
      trigger1.WaitOne();
      int x = queue.Dequeue();
      Console.WriteLine("received:"+x);
    }
  }
}

Yusuke Shinyama