なんじゃくにっき

プログラミングの話題中心。

Scala de Design Pattern: Producer-Consumer

 Producer-Consumerパターン。
http://en.wikipedia.org/wiki/Producer-consumer_problem
 
 Producer(生産者)とConsumer(消費者)がいて、
Producerが物を生産し、ConsumerがProducerが生産した物を消費する。
この際、Bufferを経由する。
ProducerはBufferに生産物を貯めていくが、
Bufferが満タンの場合、ProducerはBufferに空きが出るまで待機(もしくは生産物を破棄)する。
ConsumerはBufferに生産物がある場合はそれを取って消費する。
Bufferが空の場合は生産物が入ってくるまで待機する。
 
 
http://www.ibm.com/developerworks/jp/java/library/j-scala02049.html
を参考に書いてみた。
 
例: 

import java.util.concurrent.locks._
import java.lang.Thread._
import scala.actors._
import scala.concurrent._
import scala.util.Random

case class Empty

class Buffer(size: Int) {
val random = new Random

// 空き数を管理するためのMailBox
val mailBox1 = new MailBox

// 生産物を管理するためのMailBox
val mailBox2 = new MailBox

// mailBox1とmailBox2のキューのサイズの合計が必ずsize以下になる

// 空きをセット
for (i <- 1 to size)
mailBox1 send Empty

// Bufferに空きが有ればメッセージを送る
def put(msg : String) = {
mailBox1 receive{
case Empty => mailBox2 send msg
}
}

// Buffer内にメッセージがあれば取り出す
def take = {
mailBox2 receive{
case msg => {
mailBox1 send Empty
msg
}
}
}
}

class Producer(index: Int, buffer: Buffer) extends Actor {
val random = buffer.random
def act() = {
loop {
// ランダムな時間待つ
sleep((random.nextFloat * 500).toInt)

// ランダムな文字列を送信
val msg = random.nextInt.toString
buffer.put(msg)
println("Producer" + index + " put " + msg)
}
}
}

class Consumer(index: Int, buffer: Buffer) extends Actor {
val random = buffer.random
def act() = {
loop {
// ランダムな時間待つ
sleep((random.nextFloat * 600).toInt)
val msg = buffer.take
println("Consumer" + index + " take " + msg)
}
}
}

object Main {
def main(args: Array[String]) = {
// サイズ2のバッファーを生成
val buffer = new Buffer(2)

// Producerを3つ生成
val producers =
for(i <- 0 to 2)
yield new Producer(i, buffer)
producers.foreach(_.start())

// Consumerを3つ生成
val consumers =
for(i <- 0 to 2)
yield new Consumer(i, buffer)
consumers.foreach(_.start())
}
}

 
実行結果:
Producer1 put -1806903098
Consumer0 take -1806903098
Producer2 put 1314990851
Consumer0 take 1314990851
Producer0 put -400990397
Producer2 put -1759094004
Consumer1 take -400990397
Producer2 put -339710308
Consumer1 take -1759094004
Producer1 put 833134211
Consumer0 take -339710308
Consumer0 take 833134211
Producer2 put -2002765776
Producer2 put 620186163
Consumer0 take -2002765776
Consumer1 take 620186163
Producer1 put 1321673466
Producer2 put 1819902711
Consumer0 take 1321673466
Producer2 put 372080433
Consumer1 take 1819902711
Producer1 put -519143714
Consumer0 take 372080433
Producer2 put 1057750080
 MailBoxを2つ使うのってどうなんだろう?
誰かもっとスマートに書いて下さい。
 
 Bufferの大きさが2以上の有限の数ではなくて、1もしくは無限大のときはもう少し簡単に実装できる。
さらに、ProducerとConsumerが1対1だともっと簡単
(この場合はPipeパターンと呼ぶらしい)。