Skip to content

Instantly share code, notes, and snippets.

@jsborjesson
Last active August 29, 2015 14:22
Show Gist options
  • Select an option

  • Save jsborjesson/6588f8ed0ad839d2ea1e to your computer and use it in GitHub Desktop.

Select an option

Save jsborjesson/6588f8ed0ad839d2ea1e to your computer and use it in GitHub Desktop.
# This is a place where you can put code that should be executed once,
# and accessed by several different threads. When it is called, it will
# block the thread until the calculation has been completed, and memoize it.
#
# This means all threads that call the same method will wait for the same result,
# that is only carried out once. Think of it as a traffic light; cars arrive at the
# red light at different times, wait, and all take off together once it turns green.
class CrossThreadMemoizer
def initialize(callable)
@callable = callable
@lock = Mutex.new
@cv = ConditionVariable.new
end
def call
@lock.synchronize do
fetch_resource unless resource_called?
@cv.wait(@lock) unless resource_ready?
@resource
end
end
private
def fetch_resource
@resource_called = true
@resource = @callable.call
@cv.broadcast
end
def resource_called?
!!@resource_called
end
def resource_ready?
[email protected]?
end
end
RSpec.describe CrossThreadMemoizer do
it "calculates the result once and returns it for any thread requesting it" do
num_threads = 5
result = double
resource = double
subject = described_class.new(resource)
allow(resource).to receive(:call) { sleep 0.1; result }
results = []
threads = num_threads.times.map {
Thread.new do
results << subject.call
end
}
threads.each(&:join)
expect(results).to eq [result] * num_threads
expect(resource).to have_received(:call).once
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment