Tbpgr Blog

Employee Experience Engineer tbpgr(てぃーびー) のブログ

マルチスレッドデザインパターン | Producer-Consumerパターン 〜 れいほうシステムとねっけつ商事のアジャイル開発

概要

Producer-Consumerパターン

詳細

生産者はデータを作成するスレッド。
消費者はデータを利用するスレッド。
生産者、消費者はどちらも1〜N存在します。
両者の間に橋渡し役を用意します。
生産者と消費者が1対1の場合はPipeパターンとも呼びます。

サンプル

仕様

かんばんを利用したアジャイル開発を想定します。
システム開発会社の「株式会社れいほうシステム」ではオンサイト顧客、かんばんを実践しています。
開発現場に顧客である「株式会社ねっけつ商事」の担当者5名に常駐してもらい、要件を聞くとすぐに
開発出来る体制をとっています。
かんばんはTODO(未着手)、Done(完了)の2ステータスを可視化します。
※問題簡易化のため作業を開始したら一瞬で完了するものとします。

・顧客である「株式会社ねっけつ商事」の担当者は仕様をストーリーカードに記述してかんばんのTODOに貼り付けます。
ストーリーにはタイトルが記載されます。
・顧客が一度に依頼出来るストーリーは5件まで。
ねっけつ商事はれいほうシステムが恐ろしくて5件以上の要件を発注出来ません。
5件依頼中の場合は、依頼可能になるまで待機します。
=>Guarded Suspensionパターンを使用
・開発者はかんばんからストーリーカードを取得して開発します。
取得対象がない場合は待機します。
=>Guarded Suspensionパターンを使用

データ(Data)=>ストーリーカード
生産者(Producer)=>顧客
消費者(Consumer)=>開発者
通路役(Channel)=>かんばん

ソースコード
# encoding: utf-8
require "pp"
require "thread"

module Status
  TODO = 0
  DONE = 2
end

class StoryCard
  attr_accessor :id, :subject, :status

  def initialize(subject)
    @subject, @status = subject, Status::TODO
  end
end

class Kanban
  attr_accessor :todo_story_cards, :done_story_cards, :count
  MAX_CARDS = 5

  def initialize
    @m = Mutex.new
    @c = ConditionVariable.new
    @count = 0
    @todo_story_cards, @doing_story_cards, @done_story_cards = [],[],[]
  end

  def append(story_card, name)
    @m.synchronize {
      while max? do
        puts "waiting..."
        @c.wait @m
      end
      @count += 1
      story_card.id = @count
      @todo_story_cards << story_card
      puts "#{name} append story card #{story_card.inspect}"
      @c.signal
    }
  end

  def pull
    @m.synchronize {
      loop do
        @todo_story_cards.each do |c|
          @todo_story_cards.delete c
          c.status = Status::DONE
          @done_story_cards << c
          @c.signal
          return c 
        end
        @c.wait @m
      end
    }
  end

  private
  def max?
    active_count >= MAX_CARDS
  end

  def active_count
    @todo_story_cards.size
  end
end

class Customer < Thread
  attr_accessor :name, :kanban

  def initialize(name, kanban)
    block = lambda {
      @m = Mutex.new
      @name = name
      @kanban = kanban
      5.times {request}
    }
    super(&block)
  end

  def request
    sleep (rand 5) * 0.1
    story_card = StoryCard.new("spec")
    @kanban.append story_card, @name
  end
end

class Developer < Thread
  attr_accessor :name, :kanban

  def initialize(name, kanban)
    block = lambda {
      @name, @kanban = name, kanban
      5.times {develop}
    }
    super(&block)
  end

  def develop
    sleep (rand 5) * 0.1
    card = @kanban.pull
    puts "#{name} pull #{card.inspect}"
  end
end

kanban = Kanban.new
threads = []
threads << Customer.new("くにお", kanban)
threads << Customer.new("すがた", kanban)
threads << Customer.new("いちじょう", kanban)
threads << Customer.new("もりもと", kanban)
threads << Customer.new("ななせ", kanban)
threads << Developer.new("りゅういち", kanban)
threads << Developer.new("りゅうじ", kanban)
threads << Developer.new("もちづき",kanban)
threads << Developer.new("こばやし", kanban)
threads << Developer.new("はやさか", kanban)
threads.each {|t|t.join}

実行結果例

ななせ append story card #<StoryCard:0x6043c8 @subject="spec", @status=0, @id=1>
はやさか pull #<StoryCard:0x6043c8 @subject="spec", @status=2, @id=1>
いちじょう append story card #<StoryCard:0x5f78e8 @subject="spec", @status=0, @id=2>
こばやし pull #<StoryCard:0x5f78e8 @subject="spec", @status=2, @id=2>
くにお append story card #<StoryCard:0x5f7600 @subject="spec", @status=0, @id=3>
りゅうじ pull #<StoryCard:0x5f7600 @subject="spec", @status=2, @id=3>
くにお append story card #<StoryCard:0x5f7378 @subject="spec", @status=0, @id=4>
りゅういち pull #<StoryCard:0x5f7378 @subject="spec", @status=2, @id=4>
すがた append story card #<StoryCard:0x5f7120 @subject="spec", @status=0, @id=5>
こばやし pull #<StoryCard:0x5f7120 @subject="spec", @status=2, @id=5>
ななせ append story card #<StoryCard:0x5f6ee0 @subject="spec", @status=0, @id=6>
もちづき pull #<StoryCard:0x5f6ee0 @subject="spec", @status=2, @id=6>
くにお append story card #<StoryCard:0x5f6ca0 @subject="spec", @status=0, @id=7>
もりもと append story card #<StoryCard:0x5f6b80 @subject="spec", @status=0, @id=8>
はやさか pull #<StoryCard:0x5f6ca0 @subject="spec", @status=2, @id=7>
こばやし pull #<StoryCard:0x5f6b80 @subject="spec", @status=2, @id=8>
いちじょう append story card #<StoryCard:0x5f6808 @subject="spec", @status=0, @id=9>
りゅうじ pull #<StoryCard:0x5f6808 @subject="spec", @status=2, @id=9>
すがた append story card #<StoryCard:0x5f65c8 @subject="spec", @status=0, @id=10>
すがた append story card #<StoryCard:0x5f64a8 @subject="spec", @status=0, @id=11>
もちづき pull #<StoryCard:0x5f65c8 @subject="spec", @status=2, @id=10>
ななせ append story card #<StoryCard:0x5f6250 @subject="spec", @status=0, @id=12>
くにお append story card #<StoryCard:0x5f6130 @subject="spec", @status=0, @id=13>
りゅうじ pull #<StoryCard:0x5f64a8 @subject="spec", @status=2, @id=11>
もりもと append story card #<StoryCard:0x5f5ef0 @subject="spec", @status=0, @id=14>
りゅうじ pull #<StoryCard:0x5f6250 @subject="spec", @status=2, @id=12>
りゅういち pull #<StoryCard:0x5f6130 @subject="spec", @status=2, @id=13>
くにお append story card #<StoryCard:0x5f5b90 @subject="spec", @status=0, @id=15>
すがた append story card #<StoryCard:0x5f5a58 @subject="spec", @status=0, @id=16>
すがた append story card #<StoryCard:0x5f5920 @subject="spec", @status=0, @id=17>
いちじょう append story card #<StoryCard:0x5f57e8 @subject="spec", @status=0, @id=18>
はやさか pull #<StoryCard:0x5f5ef0 @subject="spec", @status=2, @id=14>
いちじょう append story card #<StoryCard:0x5f55c0 @subject="spec", @status=0, @id=19>
waiting...
もちづき pull #<StoryCard:0x5f5b90 @subject="spec", @status=2, @id=15>
こばやし pull #<StoryCard:0x5f5a58 @subject="spec", @status=2, @id=16>
ななせ append story card #<StoryCard:0x5f51b8 @subject="spec", @status=0, @id=20>
いちじょう append story card #<StoryCard:0x5f5488 @subject="spec", @status=0, @id=21>
waiting...
はやさか pull #<StoryCard:0x5f5920 @subject="spec", @status=2, @id=17>
りゅうじ pull #<StoryCard:0x5f57e8 @subject="spec", @status=2, @id=18>
りゅういち pull #<StoryCard:0x5f55c0 @subject="spec", @status=2, @id=19>
ななせ append story card #<StoryCard:0x5f4f90 @subject="spec", @status=0, @id=22>
こばやし pull #<StoryCard:0x5f51b8 @subject="spec", @status=2, @id=20>
もりもと append story card #<StoryCard:0x5f4a50 @subject="spec", @status=0, @id=23>
もちづき pull #<StoryCard:0x5f5488 @subject="spec", @status=2, @id=21>
もちづき pull #<StoryCard:0x5f4f90 @subject="spec", @status=2, @id=22>
りゅういち pull #<StoryCard:0x5f4a50 @subject="spec", @status=2, @id=23>
もりもと append story card #<StoryCard:0x5f4618 @subject="spec", @status=0, @id=24>
はやさか pull #<StoryCard:0x5f4618 @subject="spec", @status=2, @id=24>
もりもと append story card #<StoryCard:0x5f43d8 @subject="spec", @status=0, @id=25>
りゅういち pull #<StoryCard:0x5f43d8 @subject="spec", @status=2, @id=25>