-
-
Save zsombor/a6bd7b0440602e5333e1 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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