Skip to content

Instantly share code, notes, and snippets.

@yaauie
Last active August 29, 2015 14:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yaauie/0013cb7cda8fd63e56d0 to your computer and use it in GitHub Desktop.
Save yaauie/0013cb7cda8fd63e56d0 to your computer and use it in GitHub Desktop.
A quick spike on a one-size-fits-all semaphore in ruby; ordering is *not* guaranteed when write locks are released.
require 'thread' # Mutex
require 'monitor' # MonitorMixin::ConditionVariable
# A semaphore that supports Read Locks, Write Locks, and Sized Semaphores
class Yaauie::Semaphore
INFINITY = Float::INFINITY
# @param options [Hash{Symbol=>Object}]
# @option options [Integer, INFINITY] :readers_max (INFINITY)
def initialize(options = {})
@readers_max = options.fetch(:readers_max, INFINITY)
@mutex = Mutex.new
# Using MonitorMixin's ConditionVariable here because it supports
# wait_until; not using MonitorMixin itself, because it publicises
# synchronize and other methods used for internal locking.
@reader_queue = MonitorMixin::ConditionVariable.new(@mutex)
@writer_queue = MonitorMixin::ConditionVariable.new(@mutex)
@readers = 0
@writers = 0
end
# Run the given block in a read lock, ensuring unmutated access
# @overload read(&block)
def read
acquire_read
yield
ensure
release_read
end
alias :synchronize_read :read
# Run the given block in a write lock, ensuring exclusive access
# @overload write(&block)
def write
acquire_write
yield
ensure
release_write
end
alias :synchronize_write :write
private
def acquire_read
@mutex.synchronize do
@reader_queue.wait_until do
@writers.zero? && @readers < @readers_max
end
@readers += 1
end
end
def release_read
@mutex.synchronize do
@readers -= 1
@reader_queue.signal if @readers == (@readers_max - 1)
@writer_queue.signal if @readers.zero?
end
end
def acquire_write
@mutex.synchronize do
@writer_queue.wait_until do
@readers.zero? && @writers.zero?
end
@writers += 1
end
end
# WARNING: FIFO cannot be ensured with this approach
# because we have two queues, we cannot reliably
# wake "the next guy" up; we have to signal all.
def release_write
@mutex.synchronize do
@writers -= 1
@reader_queue.broadcast
@writer_queue.signal
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment