source: extensions/gsdl-video/trunk/installed/cmdline/lib/ruby/1.8/monitor.rb@ 18425

Last change on this file since 18425 was 18425, checked in by davidb, 15 years ago

Video extension to Greenstone

File size: 7.8 KB
Line 
1=begin
2
3= monitor.rb
4
5Copyright (C) 2001 Shugo Maeda <[email protected]>
6
7This library is distributed under the terms of the Ruby license.
8You can freely distribute/modify this library.
9
10== example
11
12This is a simple example.
13
14 require 'monitor.rb'
15
16 buf = []
17 buf.extend(MonitorMixin)
18 empty_cond = buf.new_cond
19
20 # consumer
21 Thread.start do
22 loop do
23 buf.synchronize do
24 empty_cond.wait_while { buf.empty? }
25 print buf.shift
26 end
27 end
28 end
29
30 # producer
31 while line = ARGF.gets
32 buf.synchronize do
33 buf.push(line)
34 empty_cond.signal
35 end
36 end
37
38The consumer thread waits for the producer thread to push a line
39to buf while buf.empty?, and the producer thread (main thread)
40reads a line from ARGF and push it to buf, then call
41empty_cond.signal.
42
43=end
44
45
46#
47# Adds monitor functionality to an arbitrary object by mixing the module with
48# +include+. For example:
49#
50# require 'monitor.rb'
51#
52# buf = []
53# buf.extend(MonitorMixin)
54# empty_cond = buf.new_cond
55#
56# # consumer
57# Thread.start do
58# loop do
59# buf.synchronize do
60# empty_cond.wait_while { buf.empty? }
61# print buf.shift
62# end
63# end
64# end
65#
66# # producer
67# while line = ARGF.gets
68# buf.synchronize do
69# buf.push(line)
70# empty_cond.signal
71# end
72# end
73#
74# The consumer thread waits for the producer thread to push a line
75# to buf while buf.empty?, and the producer thread (main thread)
76# reads a line from ARGF and push it to buf, then call
77# empty_cond.signal.
78#
79module MonitorMixin
80 #
81 # FIXME: This isn't documented in Nutshell.
82 #
83 # Since MonitorMixin.new_cond returns a ConditionVariable, and the example
84 # above calls while_wait and signal, this class should be documented.
85 #
86 class ConditionVariable
87 class Timeout < Exception; end
88
89 # Create a new timer with the argument timeout, and add the
90 # current thread to the list of waiters. Then the thread is
91 # stopped. It will be resumed when a corresponding #signal
92 # occurs.
93 def wait(timeout = nil)
94 @monitor.instance_eval {mon_check_owner()}
95 timer = create_timer(timeout)
96
97 Thread.critical = true
98 count = @monitor.instance_eval {mon_exit_for_cond()}
99 @waiters.push(Thread.current)
100
101 begin
102 Thread.stop
103 return true
104 rescue Timeout
105 return false
106 ensure
107 Thread.critical = true
108 if timer && timer.alive?
109 Thread.kill(timer)
110 end
111 if @waiters.include?(Thread.current) # interrupted?
112 @waiters.delete(Thread.current)
113 end
114 @monitor.instance_eval {mon_enter_for_cond(count)}
115 Thread.critical = false
116 end
117 end
118
119
120 # call #wait while the supplied block returns +true+.
121 def wait_while
122 while yield
123 wait
124 end
125 end
126
127 # call #wait until the supplied block returns +true+.
128 def wait_until
129 until yield
130 wait
131 end
132 end
133
134 # Wake up and run the next waiter
135 def signal
136 @monitor.instance_eval {mon_check_owner()}
137 Thread.critical = true
138 t = @waiters.shift
139 t.wakeup if t
140 Thread.critical = false
141 Thread.pass
142 end
143
144 # Wake up all the waiters.
145 def broadcast
146 @monitor.instance_eval {mon_check_owner()}
147 Thread.critical = true
148 for t in @waiters
149 t.wakeup
150 end
151 @waiters.clear
152 Thread.critical = false
153 Thread.pass
154 end
155
156 def count_waiters
157 return @waiters.length
158 end
159
160 private
161
162 def initialize(monitor)
163 @monitor = monitor
164 @waiters = []
165 end
166
167 def create_timer(timeout)
168 if timeout
169 waiter = Thread.current
170 return Thread.start {
171 Thread.pass
172 sleep(timeout)
173 Thread.critical = true
174 waiter.raise(Timeout.new)
175 }
176 else
177 return nil
178 end
179 end
180 end
181
182 def self.extend_object(obj)
183 super(obj)
184 obj.instance_eval {mon_initialize()}
185 end
186
187 #
188 # Attempts to enter exclusive section. Returns +false+ if lock fails.
189 #
190 def mon_try_enter
191 result = false
192 Thread.critical = true
193 if @mon_owner.nil?
194 @mon_owner = Thread.current
195 end
196 if @mon_owner == Thread.current
197 @mon_count += 1
198 result = true
199 end
200 Thread.critical = false
201 return result
202 end
203 # For backward compatibility
204 alias try_mon_enter mon_try_enter
205
206 #
207 # Enters exclusive section.
208 #
209 def mon_enter
210 Thread.critical = true
211 mon_acquire(@mon_entering_queue)
212 @mon_count += 1
213 Thread.critical = false
214 end
215
216 #
217 # Leaves exclusive section.
218 #
219 def mon_exit
220 mon_check_owner
221 Thread.critical = true
222 @mon_count -= 1
223 if @mon_count == 0
224 mon_release
225 end
226 Thread.critical = false
227 Thread.pass
228 end
229
230 #
231 # Enters exclusive section and executes the block. Leaves the exclusive
232 # section automatically when the block exits. See example under
233 # +MonitorMixin+.
234 #
235 def mon_synchronize
236 mon_enter
237 begin
238 yield
239 ensure
240 mon_exit
241 end
242 end
243 alias synchronize mon_synchronize
244
245 #
246 # FIXME: This isn't documented in Nutshell.
247 #
248 # Create a new condition variable for this monitor.
249 # This facilitates control of the monitor with #signal and #wait.
250 #
251 def new_cond
252 return ConditionVariable.new(self)
253 end
254
255 private
256
257 def initialize(*args)
258 super
259 mon_initialize
260 end
261
262 # called by initialize method to set defaults for instance variables.
263 def mon_initialize
264 @mon_owner = nil
265 @mon_count = 0
266 @mon_entering_queue = []
267 @mon_waiting_queue = []
268 end
269
270 # Throw a ThreadError exception if the current thread
271 # does't own the monitor
272 def mon_check_owner
273 if @mon_owner != Thread.current
274 raise ThreadError, "current thread not owner"
275 end
276 end
277
278 def mon_acquire(queue)
279 while @mon_owner && @mon_owner != Thread.current
280 queue.push(Thread.current)
281 Thread.stop
282 Thread.critical = true
283 end
284 @mon_owner = Thread.current
285 end
286
287 def mon_release
288 @mon_owner = nil
289 t = @mon_waiting_queue.shift
290 t = @mon_entering_queue.shift unless t
291 t.wakeup if t
292 end
293
294 def mon_enter_for_cond(count)
295 mon_acquire(@mon_waiting_queue)
296 @mon_count = count
297 end
298
299 def mon_exit_for_cond
300 count = @mon_count
301 @mon_count = 0
302 mon_release
303 return count
304 end
305end
306
307# Monitors provide means of mutual exclusion for Thread programming.
308# A critical region is created by means of the synchronize method,
309# which takes a block.
310# The condition variables (created with #new_cond) may be used
311# to control the execution of a monitor with #signal and #wait.
312#
313# the Monitor class wraps MonitorMixin, and provides aliases
314# alias try_enter try_mon_enter
315# alias enter mon_enter
316# alias exit mon_exit
317# to access its methods more concisely.
318class Monitor
319 include MonitorMixin
320 alias try_enter try_mon_enter
321 alias enter mon_enter
322 alias exit mon_exit
323end
324
325
326# Documentation comments:
327# - All documentation comes from Nutshell.
328# - MonitorMixin.new_cond appears in the example, but is not documented in
329# Nutshell.
330# - All the internals (internal modules Accessible and Initializable, class
331# ConditionVariable) appear in RDoc. It might be good to hide them, by
332# making them private, or marking them :nodoc:, etc.
333# - The entire example from the RD section at the top is replicated in the RDoc
334# comment for MonitorMixin. Does the RD section need to remain?
335# - RDoc doesn't recognise aliases, so we have mon_synchronize documented, but
336# not synchronize.
337# - mon_owner is in Nutshell, but appears as an accessor in a separate module
338# here, so is hard/impossible to RDoc. Some other useful accessors
339# (mon_count and some queue stuff) are also in this module, and don't appear
340# directly in the RDoc output.
341# - in short, it may be worth changing the code layout in this file to make the
342# documentation easier
343
344# Local variables:
345# mode: Ruby
346# tab-width: 8
347# End:
Note: See TracBrowser for help on using the repository browser.