Should class instance variables in Rails be set within a mutex?
It's possible (and desirable) to run Rails in a multi-threaded mode even in MRI. This can be accomplished by changing a line in production.rb
.
config.threadsafe!
In MRI, two threads cannot run code simultaneously, but a context switch can happen at any time. In Rubinius and JRuby, threads can run code simultaneously.
Let's look at the code you showed:
class Something
def self.objects
@objects ||= begin
# some logic that builds an array, which is ultimately stored in @objects
end
end
end
The ||=
code gets expanded to something like:
class Something
def self.objects
@objects || (@objects = begin
# some logic that builds an array, which is ultimately stored in @objects
end)
end
end
This means that there are actually two steps to the process:
- look up
@objects
- If
@objects
is falsy, set@objects
to the results of thebegin/end
expression
It may be possible for the context to switch between these steps. It is certainly possible for the context to switch in the middle of step 2. This means that you may end up running the block multiple times instead of once. In MRI, this may be acceptable, but it's perfectly straight forward to lock a mutex around the expression, so do it.
class Something
MUTEX = Mutex.new
def self.objects
MUTEX.synchronize do
@objects ||= begin
# some logic that builds an array, which is ultimately stored in @objects
end
end
end
end
Thread Safety: Class Variables in Ruby
Instance variables are not thread safe (and class variables are even less thread safe)
Example 2 and 3, both with instance variables, are equivalent, and they are NOT thread safe, like @VincentXie stated. However, here is a better example to demonstrate why they are not:
class Foo
def self.bar(message)
@bar ||= message
end
end
t1 = Thread.new do
puts "bar is #{Foo.bar('thread1')}"
end
t2 = Thread.new do
puts "bar is #{Foo.bar('thread2')}"
end
sleep 2
t1.join
t2.join
=> bar is thread1
=> bar is thread1
Because the instance variable is shared amongst all of the threads, like @VincentXie stated in his comment.
PS: Instance variables are sometimes referred to as "class instance variables", depending on the context in which they are used:
When self is a class, they are instance variables of classes(class
instance variables). When self is a object, they are instance
variables of objects(instance variables). - WindorC's answer to a question about this
Rails class method is thread safe?
This in an instance method, not to be confused with a class method. The answers
method is on an instance of User
, as opposed to being on the User
class itself. This method is caching the answers on the instance of a User
, but as long as this User
instance is being instantiated with each web request (such as a User.find()
or User.find_by()
), you’re fine because the instance is not living between threads. It’s common practice to look records up every web request in the controller, so you’re likely doing that.
If this method was on the User
class directly (such as User.answers
), then you’d need to evaluate whether it’s safe for that cached value to be maintained across threads and web requests.
To recap, the your only concern for thread safety is class methods, class variables (instance variables that use two at signs such as @@answers
), and instance methods where the instance lives on past a single web request.
If you ever find yourself needing to use a class-level variable safely, you can use Thread.current
, which is essentially a per-thread Hash (like {}) that you can store values in. For example Thread.current[:foo] = 1
would be an example. ActiveSupport uses this when setting Time.zone
.
Alternatively you may find times where you need a single array that you need to safely share across threads, in which case you’d need to look into Mutex
, which basically lets you have an array that you lock and unlock to give threads safe access to reading and writing in it. The Sidekiq gem uses a Mutex to manage workers, for example. You lock the Mutex, so that no one else can change it, then you write to it, and then unlock it. It’s important to note that if any other thread wants to write to the Mutex while it’s locked, it’ll have to wait for it to become unlocked (like, the thread just pauses while the other thread writes), so it’s important to lock as short as possible.
Setting instance variables on different objects of the same class
Never mind, I just discovered instance_variable_set
.
class Person
attr_reader :partner
def partner=(person)
# reset the old partner instance variable if it exists
partner.instance_variable_set(:@partner, nil) if partner
# set the partner attributes
@partner = person
person.instance_variable_set(:@partner, self)
end
end
Related Topics
How to Override the System Timezone in Ruby
How to Access Text Field in an Iframe
Should Repeat a Number of Times
Validates Presense VS Null False in Rails Models/Tables
How to Escape the Vertical Bar When Doing Command Line with Ruby
Return True Only If All Values Evaluate to True in Ruby
Openssl::Cipher::Ciphererror When Running Staging Db on Local
Ruby Daemon Process to Keep Objects Alive for Transient Ruby Instances
Has_Many and No Method Error Issue
Kaminari Undefined Method 'Total_Pages'
How to Dynamically Create Instance Methods at Runtime
How to Sort So That "Vitamin B12" Is Not in Front of "Vitamin B6"