備忘録的な何か

技術ブログ的な何かです

CRuby(MRI)はスレッドセーフなのか

少々炎上しそうな表題ですが、色々と調べたので覚書。

まず、前提条件として、CRubyはGIL(Global Interpreter Lock)という仕組みがあります。
細かい説明は
グローバルインタプリタロック - Wikipedia
を参照いただくとして、Rubyインタプリタは必ずGILを取得する必要があります。

となると、Thread意味無いじゃんと思ってしまうのですが、RubyにおけるGIL=GVL(Giant VM Lock)は、class Threadにあるように、IO関連処理のみ並列実行してくれます。

ネイティブスレッドを用いて実装されていますが、 現在の実装では Ruby VM は Giant VM lock (GVL) を有しており、同時に実行される ネイティブスレッドは常にひとつです。 ただし、IO 関連のブロックする可能性があるシステムコールを行う場合には GVL を解放します。その場合にはスレッドは同時に実行され得ます。 また拡張ライブラリから GVL を操作できるので、複数のスレッドを 同時に実行するような拡張ライブラリは作成可能です。


また、CRuby以外の実装について言及しますと、JRuby,RubiniusともにGILは存在せず、並列実行が可能です。

それでは、本題に戻りまして、CRubyがスレッドセーフなのかについて考えていきたいと思います。
まず、
Ruby core classes aren't thread-safe
を見ますと、Hashへのデクリメント操作の結果が、

MRI 1.9.3 : 0
JRuby 1.7.2 : 486
RBX 2.0.0rc1 : 2

となってきており、CRubyのHashはスレッドセーフに実行されていることがわかります。
(内容を端折っていますが、Hashの1つの要素に4000という値を入れて、マルチスレッドで4000回デクリメントした結果になります。)

この結果から、引用先の記事には次のような記述があります。

This is what the tweet alluded to. Our experience with MRI may lead us to think that the core classes are thread-safe, but this is an 'accident' of MRI that really shows itself as an accident when you run the same code on other runtimes

要するに、たまたまCRubyはスレッドセーフ的に動いてますよということですね。

では、次に
Nobody understands the GIL - Part 2: Implementation

を見てみますと、Charles Nutter氏(JRuby開発者)がコメントで次のように述べています。

Put simply, only standalone C functions are "atomic" in MRI.

なるほど、C言語実装の関数単独ではatomicということですね。
C functionsがatomicということは、CRubyはスレッドセーフなのか....そうはいかないらしいです。

Does the GIL Make Your Ruby Code Thread-Safe?
を見てみます。

引用先に次のようなコードが有ります。

class Sheep
  def initialize
    @shorn = false
  end

  def shorn?
    @shorn
  end

  def shear!
    puts "shearing..."
    @shorn = true
  end
end

sheep = Sheep.new

5.times.map do
  Thread.new do
    unless sheep.shorn?
      sheep.shear!
    end
  end
end.each(&:join)

元記事を読んでいただくほうが良いと思いますが、私なりにコードの説明をしますと、
Sheepクラスは、shornメソッドとshear!メソッドがあります。
毛を刈り込まれていない羊(!shorn?)が存在すれば、毛を刈り込みます(shear!)。
このコードの実装者の意図は、一匹の羊は、一度しか刈り込み(share!)を実行できないということになります。

しかし、このコードを実行すると、1つの羊に対してshear!が複数回呼ばれてしまうことがあります。
その理由は、shorn?とshare!が同期実行されないからです。

shorn?とshare!の同期実行を保証するためには、threadのQueueクラスを利用する必要があります。(引用先を参照ください。)
また、必ず shorn -> share! の順に実行されるのであれば、Mutexを導入すれば次のように解決できます。

class Sheep
  def initialize
    @shorn = false
    @mutex= Mutex.new
  end

  def shorn?
    @mutex.lock
    @shorn
  end

  def shear!
    puts "shearing..."
    @shorn = true
    @mutex.unlock
  end
end


色々と駄文を書いてきましたが、まとめますと、
・CRubyにおいて、C言語実装ライブラリ(Core Class、C拡張)はスレッドセーフで動作する。但し、GILの副次的な効果であり、今後どうなるかは分からない。
・一方で、Rubyで実装されたライブラリはスレッドセーフとならない可能性がある。
・ちなみに、Railsはスレッドセーフに動作できるので、Core Class + Railsが提供する関数は全てスレッドセーフで動作可能である。

C#のiEnumerable#SelectはEnumerator::Lazy#mapだった件

C#.net framework 3.5より、LINQ用のEnumerableメソッドが追加されています。

Select,Where,OrderByなどです。

これらのメソッドは、LINQでの使用はもちろん、ラムダ式を渡して、利用することができます。これは、Rubyのmapやfilterに当たるものになります。

参考:http://www.atmarkit.co.jp/ait/articles/1107/22/news141.html

ここで、iEnumerable#Selectのザックリとした実装を見てみますと、以下のようになっています。

public static IEnumerable<TResult> Select<TSource, TResult>(
   this IEnumerable<TSource> source, Func<TSource, TResult> block)
  {
   foreach (TSource e in source)
    yield return block(e);
  }

ここで、問題になるのが、yield return block(e);の部分になります。この構文は、実際にその要素が利用されるまで、blockを実行しません。すなわち、遅延評価となっています。LINQの実装から考えると当然ですね。

参考:http://ufcpp.net/study/csharp/sp3_lazylist.html

ですので、次のようなコードを書くとハマります。

   int y = 0;
   var l = new list<int>{1,2,3}.Select((x) => y += x);
   Console.WriteLine(y);//y= 0 (6になると思いきや、まだ評価されていない)
   l.Last();//コレで全部評価される。
   Console.WriteLine(y);//y= 6

そういえば、これはどこかで聞いたことのある話のような...。あー、Enumerator::Lazyですね。分かります。

参考:http://magazine.rubyist.net/?0041-200Special-lazy

ということで、Rubyのmap,filter相当のメソッドは実は存在しませんので、使いたいのであれば、自分で定義するしかないようです。こんな感じかな。

  public static IEnumerable<TResult> Map<TSource, TResult>(
   this IEnumerable<TSource> source,
   Func<TSource, TResult> block)
  {
   List<TResult> l = new List<TResult>();
   foreach (TSource e in source)
    l.Add( block( e ) );
   return l;
  }

  public static IEnumerable<TSource> Filter<TSource>(
   IEnumerable<TSource> source,
   Func<TSource, bool> predicate)
  {
   List<TSource> l = new List<TSource>();
   foreach (var item in source)
   {
    if (predicate(item) )
    {
     l.Add(item);
    }
   }
   return l;
  }

C#でeach_with_indexをする

Rubyのeach_with_indexって便利ですよね。

C#には、each=ForEach、map=Select、select=WhereとRubyのEnumeratorのメソッドに相当するものが有りますが、私の大好きなeach_with_index相当がありません。

 

ということで、拡張メソッドを使ってサクッとモンキーパッチしましょう。

 

static class Extensions

{

    public static void EachWithIndex<TSource>(

        this IEnumerable<TSource> source,

        Action<TSource,int> block)

    {

        int i = 0;

        foreach (TSource e in source)

            block(e,i++);

    }

}

こんな感じで使います。

new List<string> {"a","b","c"}.EachWithIndex( (x, j) => Console.WriteLine(x + j)); //=> "a0" "b1" "c2"

 

いやー、C#って便利。

ActiveRecordのTimeZoneConversionが遅い件 (Rails小ネタ)

ActiveRecordでレコードを取得する場合、以下の様なコードを書いたとする。

Article.where(cond).map{|record| #condは検索条件

  record.attributes.each{|name,value|

     #hoge

  }

}

 

この処理はレコード数が多い場合に非常に遅い。その理由は、record.attributesを実行した時に、ActiveRecord::TimeZoneConversion::Type#type_castが実行されるからである。

type_castの中の、ActiveRecord::ConnectionAdapters::Column#string_to_timeが非常に遅い。というより、Rubyの文字列をTimeに変換する関数はだいたい遅い。

 

では、どうすればよいか。Timeクラスに変換させなければよいのである。

そのためには、3つの方法がある。

①何も変換させない。(それでいいのか...)

activerecord-raw-dataを活用する。

http://subtech.g.hatena.ne.jp/secondlife/20120328/1332892079

 

②updated_atとcreated_atを取得しない。(他にTimeZoneのカラムがある場合は状況に応じて)

上記のコードをこのように変換する。

Article.where(cond).map{|record|

  record.attribute_names.each{|name| #nameだけを取得

     next if %w[updated_at created_at].include? name #TimeZoneのカラムをスキップ

        value = record[name]

        #hoge

  }

}

③attributeをギリギリまで呼び出さない。

record.updated_atやrecord["updated_at"]などでアクセスされた時に始めて、TimeZoneConversionは起こるので、実際に利用するまではそこにアクセスしない。

records = Article.where(cond).to_a  #まだTimeZoneConversionは動かない

 

・余談

めちゃくちゃ適当だが、postgresの場合は下のようにしてやればstring_to_valueが少しは早くなる。

ActiveRecord::ConnectionAdapters::Column.class_eval{

  def self.string_to_time value

    Time.local(*(value.split(/[ :\-\.]/,-1).map{|x| x.to_i}))

  end

}

Rubyのnilの挙動を利用する

皆さんご存知の通り、RubyはnilはNilClassのオブジェクトである。

 

NilClass.instance_methods(false).sortとしてみると、結構な関数が定義されている。

 => [:&, :^, :inspect, :nil?, :rationalize, :to_a, :to_c, :to_f, :to_h, :to_i, :to_r, :to_s, :|] #ruby2.0

 

ここで、それぞれのメソッドの動作を確認してみる。(記号メソッドは除く)

 

[:inspect, :nil?, :rationalize, :to_a, :to_c, :to_f, :to_h, :to_i, :to_r, :to_s].map{|meth| nil.send(meth) }

 => ["nil", true, (0/1), [ ], (0+0i), 0.0, {}, 0, (0/1), ""] 

 

ここで、面白いと思うのは、nil.to_a #=> [ ]である。

 

例えば、配列が来るかnilが来るかわからない状況があったとする。

そういう時は、activesupportのtryやpresenceを利用する場合が多い。

arr.try(:first) #=>最初の要素をとる(nilまたは空配列の時はnilを返す)

arr.presence || [ ] #=>nilか空配列の時は[ ]とする。

 

こういう時に、to_aメソッドを利用すれば同じことができる。(多分)

arr.to_a[0] #=>最初の要素をとる(nilまたは空配列の時はnilを返す)

arr.to_a #=>nilか空配列の時は[ ]とする。

 

さらに、enumerableの関数を利用する場合は、presenceより使い勝手が良い。

arr.to_a.map{|x| hoge x } #=> (arr.presence || []).map{|x| hoge x}でも可能。

 

Ruby2.0ではto_hメソッドも追加されているので、色々と面白いことができるかもしれない。

 

ちなみに、Array#to_aとHash#to_hはselfが返ってくるようだ。(Rubyist的には常識?)

a = [1,2,3]

 b = a.to_a    # => [1, 2, 3] 

 b << 2

 a                   # => [1, 2, 3, 2] 

a.equal? b    #=> true

 

a = {x:1}

b = a.to_h   #=> {:x=>1} 

b[:y] = 2

a                  #=> {:x=>1, :y=>2} 

a.equal? b    #=> true

 

ActiveRecordで複数条件のorクエリまたはinクエリを投げる(Rails小ネタ)

ActiveRecordでor検索をしようと思うと、意外と簡単にはできない。

Table.where(id: 1).or(id: 2) とかでできそうなものだができない。

 

真面目にやるには、Arelを使ってやるのが一番のようだ。

http://techracho.bpsinc.jp/tsunekawa/2013_05_24/8502

 

非常に出来合いのコードだが、or検索のscopeを作成した。(SQLインジェクション対策はなし。プレースホルダを使うscopeってどう書くの?)

 

下記のように呼び出す。

Table.or([{id: 1,val: 2},{id:4,val:5}]) #=> (id=1 and val=2) or (id=4 and val=5)

でも、結局 Table.where(id: 1).or(id: 2)はできない...(おぃ)

 

  scope :or, ->(condition){

    where(condition) unless condition.class == Array

 

    table_name = self.table_name

    sql = condition.map{|cond|

      partial = cond.map{|col,val|

        %{"#{table_name}"."#{col}" = #{sanitize(val)}}

      }.join(" AND ")

      "(#{partial})"

    }.join(" OR ")

    where(sql)

  }

 

また、PostgreSQLは複数カラムのwhere INができるので、inのscopeも作成した。http://blog.fusic.co.jp/archives/1765

下記のように呼び出す。

Table.in([{id: 1,val: 2},{id:4,val:5}]) #=> (id,val) IN ( (1,2),(4,5) )

 

  scope :in, ->(condition){

    where(condition) unless condition.class == Array

 

    table_name = self.table_name

 

    columns_sql = condition.first.map{|col,val| %{"#{table_name}"."#{col}"} }.join(" , ")

    columns = condition.first.map{|col,val| col }

 

    values = condition.map{|cond|

      value = columns.map{|col| sanitize(cond[col]) }.join(" , ")

      "(#{value})"

    }.join(" , ")

    where("(#{columns_sql}) IN (#{values})")

  }