Tbpgr Blog

Ruby プログラマ tbpgr(てぃーびー) のブログ

マルチスレッドデザインパターン | Thread-Specific Storageパターン

概要

Thread-Specific Storageパターン

詳細

スレッドの入り口は1つでも、内部で固有領域が用意されておりスレッドごとに
別々に保存・取得するパターンです。
別名
・Per-Thread Attribute
・Thread-Specific Data
・Thread-Specific Field
・Thread-Local Strage
などと呼びます。

注意点

・スレッド固有の情報をスレッド外に保持することをthread-externalという
・スレッド固有の情報をスレッド内に保持することをthread-internalという
・Thread-Specific Storageパターンは他スレッドの影響を受けない。そのため排他の仕組みを考える必要がない
・このパターンは事情があり、変更出来ないクラスをマルチスレッド対応したい場合などに有効

構成

・Client:依頼者
・TSObjectProxy:スレッド固有なオブジェクトの代理人
・TSObjectCollection:スレッド固有なオブジェクトの集まり
・TSObject:スレッド固有なオブジェクト

サンプル

仕様

あるWebサイトに設定されているユーザー単位のアクセスカウンタを想定。
1ユーザーが何度も同じページにアクセスすると、そのユーザーのアクセス数のみがインクリメントされます。

構成

・Client:サイト利用ユーザー=User
・TSObjectProxy:Webサイト=WebSite
・TSObjectCollection:アクセスカウンタ群=AccessCounters
・TSObject:アクセスカウンタ=AccessCounter

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

def random_sleep
  sleep ((rand 10) + 1) * 0.05
end

class User < Thread
  def initialize(name)
    block = lambda {
      puts "#{name} start"
      @name = name
      (1..10).each do |count|
        WebSite.access
        access_count = WebSite.access_count
        puts "#{name}'s access_count = #{access_count}"
        random_sleep
      end
      puts "#{name} end"
    }
    super(&block)
  end
end

class AccessCounters
  attr_accessor :threads
  
  def initialize
    @threads = {}
  end

  def get
    @threads[Thread.current.object_id]
  end

  def set(access_counter)
    id = Thread.current.object_id
    @threads[id] = AccessCounter.new
  end
end

class AccessCounter
  attr_accessor :count

  def initialize
    @count = 1
  end

  def access
    @count += 1
  end

  def access_count
    return @count
  end
end

class WebSite
  @@access_counters = AccessCounters.new

  class << self
    def access
      get_access_counter.access
    end

    def access_count
      get_access_counter.access_count
    end

    private
    def get_access_counter
      access_counter = @@access_counters.get
      if access_counter.nil?
        access_counter = AccessCounter.new
        @@access_counters.set access_counter
      end
      access_counter
    end
  end
end

threads = []
threads << User.new("hoge")
threads << User.new("hige")
threads << User.new("hage")

threads.each {|t|t.join}
出力結果
hoge start
hoge's access_count = 1
hige start
hige's access_count = 1
hage start
hage's access_count = 1
hage's access_count = 2
hoge's access_count = 2
hige's access_count = 2
hage's access_count = 3
hage's access_count = 4
hige's access_count = 3
hoge's access_count = 3
hage's access_count = 5
hige's access_count = 4
hoge's access_count = 4
hoge's access_count = 5
hige's access_count = 5
hage's access_count = 6
hoge's access_count = 6
hige's access_count = 6
hige's access_count = 7
hoge's access_count = 7
hage's access_count = 7
hoge's access_count = 8
hige's access_count = 8
hige's access_count = 9
hage's access_count = 8
hige's access_count = 10
hoge's access_count = 9
hoge's access_count = 10
hage's access_count = 9
hoge end
hige end
hage's access_count = 10
hage end