1# = monitor.rb 2# 3# Copyright (C) 2001 Shugo Maeda <shugo@ruby-lang.org> 4# 5# This library is distributed under the terms of the Ruby license. 6# You can freely distribute/modify this library. 7# 8 9require 'thread' 10 11# 12# In concurrent programming, a monitor is an object or module intended to be 13# used safely by more than one thread. The defining characteristic of a 14# monitor is that its methods are executed with mutual exclusion. That is, at 15# each point in time, at most one thread may be executing any of its methods. 16# This mutual exclusion greatly simplifies reasoning about the implementation 17# of monitors compared to reasoning about parallel code that updates a data 18# structure. 19# 20# You can read more about the general principles on the Wikipedia page for 21# Monitors[http://en.wikipedia.org/wiki/Monitor_%28synchronization%29] 22# 23# == Examples 24# 25# === Simple object.extend 26# 27# require 'monitor.rb' 28# 29# buf = [] 30# buf.extend(MonitorMixin) 31# empty_cond = buf.new_cond 32# 33# # consumer 34# Thread.start do 35# loop do 36# buf.synchronize do 37# empty_cond.wait_while { buf.empty? } 38# print buf.shift 39# end 40# end 41# end 42# 43# # producer 44# while line = ARGF.gets 45# buf.synchronize do 46# buf.push(line) 47# empty_cond.signal 48# end 49# end 50# 51# The consumer thread waits for the producer thread to push a line to buf 52# while <tt>buf.empty?</tt>. The producer thread (main thread) reads a 53# line from ARGF and pushes it into buf then calls <tt>empty_cond.signal</tt> 54# to notify the consumer thread of new data. 55# 56# === Simple Class include 57# 58# require 'monitor' 59# 60# class SynchronizedArray < Array 61# 62# include MonitorMixin 63# 64# def initialize(*args) 65# super(*args) 66# end 67# 68# alias :old_shift :shift 69# alias :old_unshift :unshift 70# 71# def shift(n=1) 72# self.synchronize do 73# self.old_shift(n) 74# end 75# end 76# 77# def unshift(item) 78# self.synchronize do 79# self.old_unshift(item) 80# end 81# end 82# 83# # other methods ... 84# end 85# 86# +SynchronizedArray+ implements an Array with synchronized access to items. 87# This Class is implemented as subclass of Array which includes the 88# MonitorMixin module. 89# 90module MonitorMixin 91 # 92 # FIXME: This isn't documented in Nutshell. 93 # 94 # Since MonitorMixin.new_cond returns a ConditionVariable, and the example 95 # above calls while_wait and signal, this class should be documented. 96 # 97 class ConditionVariable 98 class Timeout < Exception; end 99 100 # 101 # Releases the lock held in the associated monitor and waits; reacquires the lock on wakeup. 102 # 103 # If +timeout+ is given, this method returns after +timeout+ seconds passed, 104 # even if no other thread doesn't signal. 105 # 106 def wait(timeout = nil) 107 @monitor.__send__(:mon_check_owner) 108 count = @monitor.__send__(:mon_exit_for_cond) 109 begin 110 @cond.wait(@monitor.instance_variable_get(:@mon_mutex), timeout) 111 return true 112 ensure 113 @monitor.__send__(:mon_enter_for_cond, count) 114 end 115 end 116 117 # 118 # Calls wait repeatedly while the given block yields a truthy value. 119 # 120 def wait_while 121 while yield 122 wait 123 end 124 end 125 126 # 127 # Calls wait repeatedly until the given block yields a truthy value. 128 # 129 def wait_until 130 until yield 131 wait 132 end 133 end 134 135 # 136 # Wakes up the first thread in line waiting for this lock. 137 # 138 def signal 139 @monitor.__send__(:mon_check_owner) 140 @cond.signal 141 end 142 143 # 144 # Wakes up all threads waiting for this lock. 145 # 146 def broadcast 147 @monitor.__send__(:mon_check_owner) 148 @cond.broadcast 149 end 150 151 private 152 153 def initialize(monitor) 154 @monitor = monitor 155 @cond = ::ConditionVariable.new 156 end 157 end 158 159 def self.extend_object(obj) 160 super(obj) 161 obj.__send__(:mon_initialize) 162 end 163 164 # 165 # Attempts to enter exclusive section. Returns +false+ if lock fails. 166 # 167 def mon_try_enter 168 if @mon_owner != Thread.current 169 unless @mon_mutex.try_lock 170 return false 171 end 172 @mon_owner = Thread.current 173 end 174 @mon_count += 1 175 return true 176 end 177 # For backward compatibility 178 alias try_mon_enter mon_try_enter 179 180 # 181 # Enters exclusive section. 182 # 183 def mon_enter 184 if @mon_owner != Thread.current 185 @mon_mutex.lock 186 @mon_owner = Thread.current 187 end 188 @mon_count += 1 189 end 190 191 # 192 # Leaves exclusive section. 193 # 194 def mon_exit 195 mon_check_owner 196 @mon_count -=1 197 if @mon_count == 0 198 @mon_owner = nil 199 @mon_mutex.unlock 200 end 201 end 202 203 # 204 # Enters exclusive section and executes the block. Leaves the exclusive 205 # section automatically when the block exits. See example under 206 # +MonitorMixin+. 207 # 208 def mon_synchronize 209 mon_enter 210 begin 211 yield 212 ensure 213 mon_exit 214 end 215 end 216 alias synchronize mon_synchronize 217 218 # 219 # Creates a new MonitorMixin::ConditionVariable associated with the 220 # receiver. 221 # 222 def new_cond 223 return ConditionVariable.new(self) 224 end 225 226 private 227 228 # Use <tt>extend MonitorMixin</tt> or <tt>include MonitorMixin</tt> instead 229 # of this constructor. Have look at the examples above to understand how to 230 # use this module. 231 def initialize(*args) 232 super 233 mon_initialize 234 end 235 236 # Initializes the MonitorMixin after being included in a class or when an 237 # object has been extended with the MonitorMixin 238 def mon_initialize 239 @mon_owner = nil 240 @mon_count = 0 241 @mon_mutex = Mutex.new 242 end 243 244 def mon_check_owner 245 if @mon_owner != Thread.current 246 raise ThreadError, "current thread not owner" 247 end 248 end 249 250 def mon_enter_for_cond(count) 251 @mon_owner = Thread.current 252 @mon_count = count 253 end 254 255 def mon_exit_for_cond 256 count = @mon_count 257 @mon_owner = nil 258 @mon_count = 0 259 return count 260 end 261end 262 263# Use the Monitor class when you want to have a lock object for blocks with 264# mutual exclusion. 265# 266# require 'monitor' 267# 268# lock = Monitor.new 269# lock.synchronize do 270# # exclusive access 271# end 272# 273class Monitor 274 include MonitorMixin 275 alias try_enter try_mon_enter 276 alias enter mon_enter 277 alias exit mon_exit 278end 279 280 281# Documentation comments: 282# - All documentation comes from Nutshell. 283# - MonitorMixin.new_cond appears in the example, but is not documented in 284# Nutshell. 285# - All the internals (internal modules Accessible and Initializable, class 286# ConditionVariable) appear in RDoc. It might be good to hide them, by 287# making them private, or marking them :nodoc:, etc. 288# - RDoc doesn't recognise aliases, so we have mon_synchronize documented, but 289# not synchronize. 290# - mon_owner is in Nutshell, but appears as an accessor in a separate module 291# here, so is hard/impossible to RDoc. Some other useful accessors 292# (mon_count and some queue stuff) are also in this module, and don't appear 293# directly in the RDoc output. 294# - in short, it may be worth changing the code layout in this file to make the 295# documentation easier 296 297# Local variables: 298# mode: Ruby 299# tab-width: 8 300# End: 301