2009年08月12日

RubyでJavaのsynchronizedを

注意!コード間違ってました。インスタンスを2度生成すると、aliasが2度働いて無限ループになってしまいます。initialize内のコードが2度走らないようにする必要があります。


taken by sun dazed under CC-BY-SA

最近Rubyで遊んでいます。本当はRubyで遊ぶのが目的ではなかったのですが。今までの変遷。

PySpecの新バージョンを作ろう
→PySpecの新バージョンでは新しいアーキテクチャーに挑戦しよう
→軽量メッセージキューを実装しよう。名前は栃木っぽくberryMQ
→せっかくMQなら多言語対応しないと面白くないよね。Ruby版作ろう。
→言語間の通信はXML RPCでもいいけど、JSON RPCかな?Ctrl+Cで止まらないの不便だな
→Rubyはデコレータないのか?似たようなやつに挑戦してみるか?
→MQでPythonのAPIではメソッドの上書き使っているけど、Rubyではどうやるんだろう?←イマココ

ということで、遠回りしまくってます。で、Rubyのメソッドの上書きをやろうと思ったのですが、なかなかうまくいかず、@takaiのアドバイスを元にうまくいきました。せっかくできたのだし、toRubyでスレッドとかミューテックスあたりの勉強をしていることもあり、Javaで「内部実装はすごい」という噂のsynchronizedを実装してみました。使ってみたサンプルコードはこんな感じ。デコレータと組み合わせて、メソッド宣言の前にsynchronized宣言をpublicとかみたいに書くことで、インスタンスのロックが自動で行われるようになります。あと、Javaっぽい、メソッド内でのsynchronizeブロックもできます。

最初、synchronizedの仕様を誤解していたけど、咳さんにアドバイスをもらって、修正しました。@takaiさん、咳さん、ありがとうございました。

class TestClass
  include Synchronizable
  
  synchronized
  def test_method1(name)
    3.times do |i|
      puts "test_method1 => #{name}: #{i}"
      sleep 1
    end
  end

  def internal_lock(name)
    synchronize do
      3.times do |i|
        puts "internal_lock => #{name}: #{i}"
        sleep 1
      end
    end
  end
end
 
sample = TestClass.new()
thread1 = Thread.new {
  sample.test_method1("sub thread 1")
}
thread2 = Thread.new {
  sample.internal_lock("sub thread 2")
}
sample.test_method1("main thread")
thread1.join
thread2.join

実際のコードは「続きを読む」からどうぞ。

# -*- coding: utf-8 -*-

# Synchronize decorator for Ruby.

require 'monitor'

module SynchronizeImpl
  # Manage decorator information for each class.
  module Decorators
    @@decorators = Hash.new {|hash, class_name| 
      hash[class_name] = {:flag=>false, :methods=>[]}
    }
    # It is called when decorator method is called
    def self.activate_flag(klass)
      @@decorators[klass][:flag] = true
    end
    # It is called when new method is defined
    def self.regist_method_name(klass, method_name)
      decorators = @@decorators[klass]
      if decorators[:flag] == true
        decorators[:methods].push(method_name)
        decorators[:flag] = false
      end
    end
    # Return all decorators of significant class
    def self.get(klass)
      @@decorators[klass][:methods]
    end
  end

  # This module defines actual decorator methods.
  # Target object is extended by this module.
  module DecoratorAPI
    def method_added(method)
      Decorators::regist_method_name(self, method)
    end
    # Synchronize decorator method
    def synchronized
      Decorators::activate_flag(self)
    end
  end
end

# User uses this module
module Synchronizable
  def self.included(base)
    base.extend(SynchronizeImpl::DecoratorAPI)
  end

  def initialize
    @monitor = Monitor.new
    SynchronizeImpl::Decorators::get(self.class).each do |target_method|
      original_method = :"original_#{target_method}"
      self.class.class_eval do
        p target_method
        alias_method original_method, target_method
        define_method target_method do |*args|
          @monitor.synchronize do
            __send__ original_method, args
          end
        end
      end
    end
  end

  def synchronize
    @monitor.synchronize {
      yield
    }
  end
end

文字列の前にコロンを置くとシンボルになるって知りませんでした。これで変数展開を使って動的にシンボルが作れるんですね。intern使わなくても。Ruby技術者認定試験にパスしていていも、知らないことはたくさんあります。

posted by @shibukawa at 10:02 | Comment(41) | TrackBack(1) | Ruby はてなブックマーク - RubyでJavaのsynchronizedを
この記事へのトラックバックURL
http://blog.sakura.ne.jp/tb/31246705
※ブログオーナーが承認したトラックバックのみ表示されます。

この記事へのトラックバック

takano32's status on Wednesday, 12-Aug-09 01:25:05 UTC
Excerpt: これとか読んでてよくわかんなくなってきた たぶんPythonあたりからの流儀が入ってる http://blog.shibu.jp/article/31246705.html
Weblog: takano32
Tracked: 2009-08-12 10:25
検索ボックス

Twitter

www.flickr.com
This is a Flickr badge showing public photos and videos from shibukawa.yoshiki. Make your own badge here.
<< 2019年02月 >>
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28    
最近の記事
カテゴリ
過去ログ
Powered by さくらのブログ