diff options
Diffstat (limited to 'lib/monitor.rb')
| -rw-r--r-- | lib/monitor.rb | 331 |
1 files changed, 141 insertions, 190 deletions
diff --git a/lib/monitor.rb b/lib/monitor.rb index 31234819b8..21329a5de7 100644 --- a/lib/monitor.rb +++ b/lib/monitor.rb @@ -1,157 +1,110 @@ -=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. - -== 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' - +# frozen_string_literal: false +# = 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. +# +# +# 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. # -# Adds monitor functionality to an arbitrary object by mixing the module with -# +include+. For example: +# You can read more about the general principles on the Wikipedia page for +# Monitors[https://en.wikipedia.org/wiki/Monitor_%28synchronization%29]. # -# require 'monitor' -# -# 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. +# == 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 + ConditionVariable = Monitor::ConditionVariable # :nodoc: + # # 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 - class Timeout < Exception; end - - def wait(timeout = nil) - if timeout - raise NotImplementedError, "timeout is not implemented yet" - end - @monitor.send(:mon_check_owner) - count = @monitor.send(:mon_exit_for_cond) - begin - @cond.wait(@monitor.instance_variable_get("@mon_mutex")) - return true - ensure - @monitor.send(:mon_enter_for_cond, count) - end - end - - def wait_while - while yield - wait - end - end - - def wait_until - until yield - wait - end - end - - def signal - @monitor.send(:mon_check_owner) - @cond.signal - end - - def broadcast - @monitor.send(:mon_check_owner) - @cond.broadcast - end - - def count_waiters - raise NotImplementedError - end - - private - def initialize(monitor) - @monitor = monitor - @cond = ::ConditionVariable.new - end - end - - def self.extend_object(obj) + def self.extend_object(obj) # :nodoc: super(obj) - obj.send(:mon_initialize) + obj.__send__(:mon_initialize) end - + # # Attempts to enter exclusive section. Returns +false+ if lock fails. # def mon_try_enter - if @mon_owner != Thread.current - unless @mon_mutex.try_lock - return false - end - @mon_owner = Thread.current - end - @mon_count += 1 - return true + @mon_data.try_enter end # For backward compatibility alias try_mon_enter mon_try_enter @@ -160,23 +113,29 @@ module MonitorMixin # Enters exclusive section. # def mon_enter - if @mon_owner != Thread.current - @mon_mutex.lock - @mon_owner = Thread.current - end - @mon_count += 1 + @mon_data.enter end - + # # Leaves exclusive section. # def mon_exit mon_check_owner - @mon_count -=1 - if @mon_count == 0 - @mon_owner = nil - @mon_mutex.unlock - end + @mon_data.exit + end + + # + # Returns true if this monitor is locked by any thread + # + def mon_locked? + @mon_data.mon_locked? + end + + # + # Returns true if this monitor is locked by current thread. + # + def mon_owned? + @mon_data.mon_owned? end # @@ -184,63 +143,62 @@ module MonitorMixin # section automatically when the block exits. See example under # +MonitorMixin+. # - def mon_synchronize - mon_enter - begin - yield - ensure - mon_exit - end + def mon_synchronize(&b) + @mon_data.synchronize(&b) end alias synchronize mon_synchronize - + # - # FIXME: This isn't documented in Nutshell. + # Creates a new MonitorMixin::ConditionVariable associated with the + # Monitor object. # def new_cond - return ConditionVariable.new(self) + unless defined?(@mon_data) + mon_initialize + @mon_initialized_by_new_cond = true + end + return ConditionVariable.new(@mon_data) end private - def initialize(*args) + # 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(...) super mon_initialize end + # Initializes the MonitorMixin after being included in a class or when an + # object has been extended with the MonitorMixin def mon_initialize - @mon_owner = nil - @mon_count = 0 - @mon_mutex = Mutex.new - end - - def mon_check_owner - if @mon_owner != Thread.current - raise ThreadError, "current thread not owner" + if defined?(@mon_data) + if defined?(@mon_initialized_by_new_cond) + return # already initialized. + elsif @mon_data_owner_object_id == self.object_id + raise ThreadError, "already initialized" + end end + @mon_data = ::Monitor.new + @mon_data_owner_object_id = self.object_id end - def mon_enter_for_cond(count) - @mon_owner = Thread.current - @mon_count = count - end - - def mon_exit_for_cond - count = @mon_count - @mon_owner = nil - @mon_count = 0 - return count + # Ensures that the MonitorMixin is owned by the current thread, + # otherwise raises an exception. + def mon_check_owner + @mon_data.mon_check_owner end end -class Monitor - include MonitorMixin - alias try_enter try_mon_enter - alias enter mon_enter - alias exit mon_exit +class Monitor # :nodoc: + alias try_mon_enter try_enter + alias mon_try_enter try_enter + alias mon_enter enter + alias mon_exit exit + alias mon_synchronize synchronize end - # Documentation comments: # - All documentation comes from Nutshell. # - MonitorMixin.new_cond appears in the example, but is not documented in @@ -248,8 +206,6 @@ end # - 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 @@ -258,8 +214,3 @@ end # directly in the RDoc output. # - in short, it may be worth changing the code layout in this file to make the # documentation easier - -# Local variables: -# mode: Ruby -# tab-width: 8 -# End: |
