forked from ruby/ruby
-
Notifications
You must be signed in to change notification settings - Fork 0
/
monitor.rb
302 lines (277 loc) · 6.98 KB
/
monitor.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# = 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.
#
require 'thread'
#
# 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.
#
# 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
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)
@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
#
# Calls wait repeatedly while the given block yields a truthy value.
#
def wait_while
while yield
wait
end
end
#
# Calls wait repeatedly until the given block yields a truthy value.
#
def wait_until
until yield
wait
end
end
#
# Wakes up the first thread in line waiting for this lock.
#
def signal
@monitor.__send__(:mon_check_owner)
@cond.signal
end
#
# Wakes up all threads waiting for this lock.
#
def broadcast
@monitor.__send__(:mon_check_owner)
@cond.broadcast
end
private
def initialize(monitor)
@monitor = monitor
@cond = ::ConditionVariable.new
end
end
def self.extend_object(obj)
super(obj)
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
@mon_count = 0
end
@mon_count += 1
return true
end
# For backward compatibility
alias try_mon_enter mon_try_enter
#
# Enters exclusive section.
#
def mon_enter
if @mon_owner != Thread.current
@mon_mutex.lock
@mon_owner = Thread.current
@mon_count = 0
end
@mon_count += 1
end
#
# Leaves exclusive section.
#
def mon_exit
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
# section automatically when the block exits. See example under
# +MonitorMixin+.
#
def mon_synchronize
mon_enter
begin
yield
ensure
mon_exit
end
end
alias synchronize mon_synchronize
#
# Creates a new MonitorMixin::ConditionVariable associated with the
# receiver.
#
def new_cond
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
# 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"
end
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
end
end
# Use the Monitor class when you want to have a lock object for blocks with
# mutual exclusion.
#
# 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
end
# Documentation comments:
# - All documentation comes from Nutshell.
# - 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.
# - 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
# 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: