# Some examples for why the | |
# | |
# @obj ||= "some value" | |
# | |
# expression is threadsafe on MRI (should one be dumb enough to write | |
# such code) | |
# | |
# MRI will only preempt a Ruby thread when it is returning from a | |
# method call. If a code block contains no ruby method calls then | |
# then it will not be preempted. If it contains a single method | |
# call then the preemption point is predictable. | |
# | |
# This code: | |
# | |
# @obj ||= "some value" | |
# | |
# expands to: | |
# | |
# if @obj | |
# @obj = "some value" | |
# end | |
# | |
# and for this to be unsafe it should contain two method calls. There | |
# are none or one depending on how you count it. | |
# | |
# Now if I were to change the code to something like this | |
# | |
# @obj ||= Whatever.new | |
# | |
# then now there is a function call between the CHECK and SET | |
# operations and it is no longer thread safe. | |
# | |
# | |
# Regardless of how safe the original code was, the best thing to do | |
# is avoiding shared state when working with threads: | |
# | |
# Don't communicate by sharing memory; share memory by communicating | |
# (Go Programming Language). | |
class DoesNotPreemptWithoutMethodCalls | |
def run | |
@whatever = nil | |
@non_nil_count = 0 | |
@thread_count = 1_000 | |
@thread_count.times.map do | |
Thread.new do | |
if @whatever != nil | |
@non_nil_count += 1 | |
end | |
@whatever ||= 1 # ??? Thread safe ??? | |
end | |
end.each(&:join) | |
if @thread_count == @non_nil_count + 1 | |
puts "OK" | |
else | |
puts "BUM: Preempted without a method call!" | |
end | |
end | |
end | |
DoesNotPreemptWithoutMethodCalls.new.run | |
class HaveSomeMethodcallsForPreemtion | |
def count_non_nils | |
if @whatever != nil | |
@non_nil_count += 1 | |
end | |
# may preempt here | |
end | |
def run | |
@whatever = nil | |
@non_nil_count = 0 | |
@thread_count = 1_000 | |
@thread_count.times.map do | |
Thread.new do | |
count_non_nils # may be preempted here | |
@whatever ||= 1 # but not here | |
end | |
end.each(&:join) | |
if @thread_count == @non_nil_count + 1 | |
puts "2 - OK" | |
else | |
puts "2 - BUM: Preempted without a method call!" | |
end | |
end | |
end | |
HaveSomeMethodcallsForPreemtion.new.run |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment