はじめに
Railsのインスタンス変数・クラス変数・クラスインスタンス変数は、リクエストやスレッドに対してどのようなスコープを持つようになっているのか、調べてみます。
調べ方ですが、以下のような、インスタンス変数・クラス変数・クラスインスタンス変数をインクリメントするcontrollerクラスを作成します。pumaのプロセス数・スレッド数の設定を変えて、postsコントローラ(http://localhost/posts) へ連続してアクセスし、各変数のふるまいの違いを見てみます。
# posts_controller.rb
class PostsController < InheritedResources::Base
@@class_var = 0
@class_instance_var = 0
def initialize
@instance_var = 0
super
end
def index
@@class_var += 1
@instance_var += 1
puts 'class var: %d' % @@class_var
puts 'instance var: %d' % @instance_var
self.class.increment_civ
puts '------------------------'
end
def self.increment_civ
@class_instance_var += 1
puts 'class instance var: %d' % @class_instance_var
end
end
# config/puma.rb
workers 1 # プロセス数
threads_count = 1 # スレッド数
threads threads_count, threads_count
今回使用した環境は、以下のとおりです。
- Rubyのバージョン:
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-linux]
- Railsのバージョン:
rails (5.0.0.1)
- rackのバージョン:
rack (2.0.1)
- pumaのバージョン:
puma (3.6.0)
プロセス数・スレッド数を変えて実験
1プロセス・1スレッドの場合
class var: 1
instance var: 1
class instance var: 1
------------------------
class var: 2
instance var: 1
class instance var: 2
------------------------
class var: 3
instance var: 1
class instance var: 3
------------------------
class var: 4
instance var: 1
class instance var: 4
------------------------
クラス変数・クラスインスタンス変数がインクリメントされていきます。クラス変数・クラスインスタンス変数が、リクエストをまたいで使い回されていることがわかります。
一方で、インスタンス変数の値はすべて1のままです。インスタンス変数はリクエストごとに作成されることがわかります。
2プロセス・1スレッドの場合
class var: 1
instance var: 1
class instance var: 1
------------------------
class var: 1
instance var: 1
class instance var: 1
------------------------
class var: 2
instance var: 1
class instance var: 2
------------------------
class var: 3
instance var: 1
class instance var: 3
------------------------
class var: 2
instance var: 1
class instance var: 2
------------------------
class var: 3
instance var: 1
class instance var: 3
------------------------
クラス変数・クラスインスタンス変数が二手に分かれてインクリメントされていきます。プロセスごとに別のクラス変数・クラスインスタンス変数が作成されていることがわかります。
インスタンス変数の値は、相変わらずすべて1のままです。
2プロセス・2スレッドの場合
class var: 1
instance var: 1
class instance var: 1
------------------------
class var: 1
instance var: 1
class instance var: 1
------------------------
class var: 2
instance var: 1
class instance var: 2
------------------------
class var: 2
instance var: 1
class instance var: 2
------------------------
class var: 3
instance var: 1
class instance var: 3
------------------------
class var: 3
instance var: 1
class instance var: 3
------------------------
2プロセス・1スレッドの場合と同様に、クラス変数・クラスインスタンス変数が、二手に分かれてインクリメントされる結果となりました。同じプロセスの異なるスレッドでは、クラス変数・クラスインスタンス変数がスレッドをまたいで使い回されていることがわかります。
インスタンス変数の値は、相変わらずすべて1のままです。
config.cache_classes = false
にすると・・・
config.cache_classes = false
にして任意のクラスを編集すると、クラス変数・クラスインスタンス変数の値がリセットされました。クラス変数・クラスインスタンス変数の使い回しはクラスのキャッシュによるもので、クラスがリロードされるとクラス変数・クラスインスタンス変数もあらためて初期化されることがわかります。
結論
- クラス変数・クラスインスタンス変数は、リクエスト・スレッドをまたいで同じキャッシュが使われる。プロセスが異なれば、プロセスごとに別のキャッシュが使われる。
- インスタンス変数は、プロセス・リクエスト・スレッドごとに作成される。
- クラスのキャッシュがリロードされると、クラス変数・クラスインスタンス変数も初期化される。
クラス変数・クラスインスタンス変数を使うときは、気をつけましょう!