lib/rubysl/monitor/monitor.rb in rubysl-monitor-1.0.0 vs lib/rubysl/monitor/monitor.rb in rubysl-monitor-2.0.0
- old
+ new
@@ -1,184 +1,204 @@
-=begin
+# = monitor.rb
+#
+# Copyright (C) 2001 Shugo Maeda <shugo@ruby-lang.org>
+#
+# This library is distributed under the terms of the Ruby license.
+# You can freely distribute/modify this library.
+#
-= monitor.rb
-
-Copyright (C) 2001 Shugo Maeda <shugo@ruby-lang.org>
-Copyright (C) 2008 MenTaLguY <mental@rydia.net>
-
-This library is distributed under the terms of the Ruby license.
-You can freely distribute/modify this library.
-
-== example
-
-This is a simple example.
-
- require 'monitor.rb'
-
- buf = []
- buf.extend(MonitorMixin)
- empty_cond = buf.new_cond
-
- # consumer
- Thread.start do
- loop do
- buf.synchronize do
- empty_cond.wait_while { buf.empty? }
- print buf.shift
- end
- end
- end
-
- # producer
- while line = ARGF.gets
- buf.synchronize do
- buf.push(line)
- empty_cond.signal
- end
- end
-
-The consumer thread waits for the producer thread to push a line
-to buf while buf.empty?, and the producer thread (main thread)
-reads a line from ARGF and push it to buf, then call
-empty_cond.signal.
-
-=end
-
require 'thread'
#
-# Adds monitor functionality to an arbitrary object by mixing the module with
-# +include+. For example:
+# In concurrent programming, a monitor is an object or module intended to be
+# used safely by more than one thread. The defining characteristic of a
+# monitor is that its methods are executed with mutual exclusion. That is, at
+# each point in time, at most one thread may be executing any of its methods.
+# This mutual exclusion greatly simplifies reasoning about the implementation
+# of monitors compared to reasoning about parallel code that updates a data
+# structure.
#
-# require 'monitor.rb'
-#
-# buf = []
-# buf.extend(MonitorMixin)
-# empty_cond = buf.new_cond
-#
-# # consumer
-# Thread.start do
-# loop do
-# buf.synchronize do
-# empty_cond.wait_while { buf.empty? }
-# print buf.shift
-# end
-# end
-# end
-#
-# # producer
-# while line = ARGF.gets
-# buf.synchronize do
-# buf.push(line)
-# empty_cond.signal
-# end
-# end
-#
-# The consumer thread waits for the producer thread to push a line
-# to buf while buf.empty?, and the producer thread (main thread)
-# reads a line from ARGF and push it to buf, then call
-# empty_cond.signal.
+# You can read more about the general principles on the Wikipedia page for
+# Monitors[http://en.wikipedia.org/wiki/Monitor_%28synchronization%29]
#
+# == Examples
+#
+# === Simple object.extend
+#
+# require 'monitor.rb'
+#
+# buf = []
+# buf.extend(MonitorMixin)
+# empty_cond = buf.new_cond
+#
+# # consumer
+# Thread.start do
+# loop do
+# buf.synchronize do
+# empty_cond.wait_while { buf.empty? }
+# print buf.shift
+# end
+# end
+# end
+#
+# # producer
+# while line = ARGF.gets
+# buf.synchronize do
+# buf.push(line)
+# empty_cond.signal
+# end
+# end
+#
+# The consumer thread waits for the producer thread to push a line to buf
+# while <tt>buf.empty?</tt>. The producer thread (main thread) reads a
+# line from ARGF and pushes it into buf then calls <tt>empty_cond.signal</tt>
+# to notify the consumer thread of new data.
+#
+# === Simple Class include
+#
+# require 'monitor'
+#
+# class SynchronizedArray < Array
+#
+# include MonitorMixin
+#
+# def initialize(*args)
+# super(*args)
+# end
+#
+# alias :old_shift :shift
+# alias :old_unshift :unshift
+#
+# def shift(n=1)
+# self.synchronize do
+# self.old_shift(n)
+# end
+# end
+#
+# def unshift(item)
+# self.synchronize do
+# self.old_unshift(item)
+# end
+# end
+#
+# # other methods ...
+# end
+#
+# +SynchronizedArray+ implements an Array with synchronized access to items.
+# This Class is implemented as subclass of Array which includes the
+# MonitorMixin module.
+#
module MonitorMixin
#
# FIXME: This isn't documented in Nutshell.
#
# Since MonitorMixin.new_cond returns a ConditionVariable, and the example
# above calls while_wait and signal, this class should be documented.
#
class ConditionVariable
- # Create a new timer with the argument timeout, and add the
- # current thread to the list of waiters. Then the thread is
- # stopped. It will be resumed when a corresponding #signal
- # occurs.
+ class Timeout < Exception; end
+
+ #
+ # Releases the lock held in the associated monitor and waits; reacquires the lock on wakeup.
+ #
+ # If +timeout+ is given, this method returns after +timeout+ seconds passed,
+ # even if no other thread doesn't signal.
+ #
def wait(timeout = nil)
- condition = @condition
- @monitor.instance_eval { mon_wait_for_cond(condition, timeout) }
+ @monitor.__send__(:mon_check_owner)
+ count = @monitor.__send__(:mon_exit_for_cond)
+ begin
+ @cond.wait(@monitor.instance_variable_get(:@mon_mutex), timeout)
+ return true
+ ensure
+ @monitor.__send__(:mon_enter_for_cond, count)
+ end
end
- # call #wait while the supplied block returns +true+.
+ #
+ # Calls wait repeatedly while the given block yields a truthy value.
+ #
def wait_while
while yield
- wait
+ wait
end
end
-
- # call #wait until the supplied block returns +true+.
+
+ #
+ # Calls wait repeatedly until the given block yields a truthy value.
+ #
def wait_until
until yield
- wait
+ wait
end
end
-
- # Wake up and run the next waiter
+
+ #
+ # Wakes up the first thread in line waiting for this lock.
+ #
def signal
- condition = @condition
- @monitor.instance_eval { mon_signal_cond(condition) }
- nil
+ @monitor.__send__(:mon_check_owner)
+ @cond.signal
end
-
- # Wake up all the waiters.
+
+ #
+ # Wakes up all threads waiting for this lock.
+ #
def broadcast
- condition = @condition
- @monitor.instance_eval { mon_broadcast_cond(condition) }
- nil
+ @monitor.__send__(:mon_check_owner)
+ @cond.broadcast
end
-
- def count_waiters
- condition = @condition
- @monitor.instance_eval { mon_count_cond_waiters(condition) }
- end
-
+
private
- def initialize(monitor, condition)
+ def initialize(monitor)
@monitor = monitor
- @condition = condition
+ @cond = ::ConditionVariable.new
end
end
-
+
def self.extend_object(obj)
super(obj)
- obj.instance_eval {mon_initialize()}
+ obj.__send__(:mon_initialize)
end
-
+
#
# Attempts to enter exclusive section. Returns +false+ if lock fails.
#
def mon_try_enter
- @mon_mutex.synchronize do
- @mon_owner = Thread.current unless @mon_owner
- if @mon_owner == Thread.current
- @mon_count += 1
- true
- else
- false
+ if @mon_owner != Thread.current
+ unless @mon_mutex.try_lock
+ return false
end
+ @mon_owner = Thread.current
end
+ @mon_count += 1
+ return true
end
# For backward compatibility
alias try_mon_enter mon_try_enter
#
# Enters exclusive section.
#
def mon_enter
- @mon_mutex.synchronize do
- mon_acquire(@mon_entering_cond)
- @mon_count += 1
+ if @mon_owner != Thread.current
+ @mon_mutex.lock
+ @mon_owner = Thread.current
end
+ @mon_count += 1
end
-
+
#
# Leaves exclusive section.
#
def mon_exit
- @mon_mutex.synchronize do
- mon_check_owner
- @mon_count -= 1
- mon_release if @mon_count.zero?
- nil
+ mon_check_owner
+ @mon_count -=1
+ if @mon_count == 0
+ @mon_owner = nil
+ @mon_mutex.unlock
end
end
#
# Enters exclusive section and executes the block. Leaves the exclusive
@@ -192,125 +212,66 @@
ensure
mon_exit
end
end
alias synchronize mon_synchronize
-
+
#
- # FIXME: This isn't documented in Nutshell.
- #
- # Create a new condition variable for this monitor.
- # This facilitates control of the monitor with #signal and #wait.
+ # Creates a new MonitorMixin::ConditionVariable associated with the
+ # receiver.
#
def new_cond
- condition = ::ConditionVariable.new
- condition.instance_eval { @mon_n_waiters = 0 }
- return ConditionVariable.new(self, condition)
+ return ConditionVariable.new(self)
end
private
+ # Use <tt>extend MonitorMixin</tt> or <tt>include MonitorMixin</tt> instead
+ # of this constructor. Have look at the examples above to understand how to
+ # use this module.
def initialize(*args)
super
mon_initialize
end
- # called by initialize method to set defaults for instance variables.
+ # Initializes the MonitorMixin after being included in a class or when an
+ # object has been extended with the MonitorMixin
def mon_initialize
- @mon_mutex = Mutex.new
@mon_owner = nil
@mon_count = 0
- @mon_total_waiting = 0
- @mon_entering_cond = ::ConditionVariable.new
- @mon_waiting_cond = ::ConditionVariable.new
- self
+ @mon_mutex = Mutex.new
end
- # Throw a ThreadError exception if the current thread
- # does't own the monitor
def mon_check_owner
- # called with @mon_mutex held
if @mon_owner != Thread.current
raise ThreadError, "current thread not owner"
end
end
- def mon_acquire(condition)
- # called with @mon_mutex held
- while @mon_owner && @mon_owner != Thread.current
- condition.wait @mon_mutex
- end
+ def mon_enter_for_cond(count)
@mon_owner = Thread.current
+ @mon_count = count
end
- def mon_release
- # called with @mon_mutex held
+ def mon_exit_for_cond
+ count = @mon_count
@mon_owner = nil
- if @mon_total_waiting.nonzero?
- @mon_waiting_cond.signal
- else
- @mon_entering_cond.signal
- end
+ @mon_count = 0
+ return count
end
-
- def mon_wait_for_cond(condition, timeout)
- @mon_mutex.synchronize do
- mon_check_owner
- count = @mon_count
- @mon_count = 0
- condition.instance_eval { @mon_n_waiters += 1 }
- begin
- mon_release
- if timeout
- condition.wait(@mon_mutex, timeout)
- else
- condition.wait(@mon_mutex)
- true
- end
- ensure
- @mon_total_waiting += 1
- # TODO: not interrupt-safe
- mon_acquire(@mon_waiting_cond)
- @mon_total_waiting -= 1
- @mon_count = count
- condition.instance_eval { @mon_n_waiters -= 1 }
- end
- end
- end
-
- def mon_signal_cond(condition)
- @mon_mutex.synchronize do
- mon_check_owner
- condition.signal
- end
- end
-
- def mon_broadcast_cond(condition)
- @mon_mutex.synchronize do
- mon_check_owner
- condition.broadcast
- end
- end
-
- def mon_count_cond_waiters(condition)
- @mon_mutex.synchronize do
- condition.instance_eval { @mon_n_waiters }
- end
- end
end
-# Monitors provide means of mutual exclusion for Thread programming.
-# A critical region is created by means of the synchronize method,
-# which takes a block.
-# The condition variables (created with #new_cond) may be used
-# to control the execution of a monitor with #signal and #wait.
+# Use the Monitor class when you want to have a lock object for blocks with
+# mutual exclusion.
#
-# the Monitor class wraps MonitorMixin, and provides aliases
-# alias try_enter try_mon_enter
-# alias enter mon_enter
-# alias exit mon_exit
-# to access its methods more concisely.
+# require 'monitor'
+#
+# lock = Monitor.new
+# lock.synchronize do
+# # exclusive access
+# end
+#
class Monitor
include MonitorMixin
alias try_enter try_mon_enter
alias enter mon_enter
alias exit mon_exit
@@ -322,11 +283,9 @@
# - MonitorMixin.new_cond appears in the example, but is not documented in
# Nutshell.
# - All the internals (internal modules Accessible and Initializable, class
# ConditionVariable) appear in RDoc. It might be good to hide them, by
# making them private, or marking them :nodoc:, etc.
-# - The entire example from the RD section at the top is replicated in the RDoc
-# comment for MonitorMixin. Does the RD section need to remain?
# - RDoc doesn't recognise aliases, so we have mon_synchronize documented, but
# not synchronize.
# - mon_owner is in Nutshell, but appears as an accessor in a separate module
# here, so is hard/impossible to RDoc. Some other useful accessors
# (mon_count and some queue stuff) are also in this module, and don't appear