Last active
August 29, 2015 14:05
-
-
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.
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
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