Task Parallel Library で producer-consumer パターン


.NET Framework 4 になり Task Parallel Library (TPL) が追加され,並列処理が容易に行えるようになりました。

並列プログラミングでよく用いられるパターンに producer-consumer パターンがあります。 TPL を用いた producer-consumer の例を紹介します。

var random = new Random();
var queue = new ConcurrentQueue<Person>();
bool closed = false;
int proceeds = 0;

Action producer = () =>
{
	do
	{
		int money;
		lock (random)
		{
			money = random.Next(1000, 10000);
			if (random.NextDouble() > 0.99)
			{
				closed = true;
			}
		}
		Person customer = new Person() { PocketMoney = money };
		queue.Enqueue(customer);
	} while (!closed);
};
Action consumer = () =>
{
	Person customer;
	while (!closed || !queue.IsEmpty)
	{
		if (queue.TryDequeue(out customer))
		{
			int take;
			lock (random)
			{
				take = random.Next(customer.PocketMoney);
			}
			Interlocked.Add(ref proceeds, take);
		}
	}
};

Parallel.Invoke(producer, consumer, consumer);
Console.WriteLine("売上金: {0:#,0}円", proceeds);

class Person
{
	public int PocketMoney { get; set; }
}

producer は顧客が待ち行列を作って店に並ぶイメージです。 consumer は顧客に応対してポケットマネーを頂戴します。最終的に consumer が稼いだ金額の合計をコンソールに出力します。ここでは 1-producer-N-consumer の例を示していますが,もちろん 1-1 や N-N に対応することも容易です。

この例では ConcurrentQueue クラスを用いていますが,他にもいくつかのスレッドセーフなコレクションが System.Collections.Concurrent 名前空間に定義されています。個人的な感想としては,順序を規定しないコレクションである ConcurrentBag クラスが用意されているのが使いやすくて良いと思います。