1
1
require 'thread'
2
2
require 'concurrent/atomic/atomic_reference'
3
3
require 'concurrent/errors'
4
+ require 'concurrent/synchronization'
4
5
5
6
module Concurrent
6
7
@@ -26,19 +27,19 @@ module Concurrent
26
27
# This will lead to deadlock
27
28
#
28
29
# @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html java.util.concurrent.ReentrantReadWriteLock
29
- class ReadWriteLock
30
+ class ReadWriteLock < Synchronization :: Object
30
31
31
32
# @!visibility private
32
- WAITING_WRITER = 1 << 15
33
+ WAITING_WRITER = 1 << 15
33
34
34
35
# @!visibility private
35
- RUNNING_WRITER = 1 << 30
36
+ RUNNING_WRITER = 1 << 30
36
37
37
38
# @!visibility private
38
- MAX_READERS = WAITING_WRITER - 1
39
+ MAX_READERS = WAITING_WRITER - 1
39
40
40
41
# @!visibility private
41
- MAX_WRITERS = RUNNING_WRITER - MAX_READERS - 1
42
+ MAX_WRITERS = RUNNING_WRITER - MAX_READERS - 1
42
43
43
44
# Implementation notes:
44
45
# A goal is to make the uncontended path for both readers/writers lock-free
@@ -53,11 +54,11 @@ class ReadWriteLock
53
54
54
55
# Create a new `ReadWriteLock` in the unlocked state.
55
56
def initialize
56
- @counter = AtomicReference . new ( 0 ) # single integer which represents lock state
57
- @reader_q = ConditionVariable . new # queue for waiting readers
58
- @reader_mutex = Mutex . new # to protect reader queue
59
- @writer_q = ConditionVariable . new # queue for waiting writers
60
- @writer_mutex = Mutex . new # to protect writer queue
57
+ @Counter = AtomicReference . new ( 0 ) # single integer which represents lock state
58
+ @ReadLock = Synchronization :: Lock . new
59
+ @WriteLock = Synchronization :: Lock . new
60
+ ensure_ivar_visibility!
61
+ super ( )
61
62
end
62
63
63
64
# Execute a block operation within a read lock.
@@ -106,47 +107,41 @@ def with_write_lock
106
107
# @raise [Concurrent::ResourceLimitError] if the maximum number of readers
107
108
# is exceeded.
108
109
def acquire_read_lock
109
- while ( true )
110
- c = @counter . value
110
+ while true
111
+ c = @Counter . value
111
112
raise ResourceLimitError . new ( 'Too many reader threads' ) if max_readers? ( c )
112
113
113
114
# If a writer is waiting when we first queue up, we need to wait
114
115
if waiting_writer? ( c )
115
- # But it is possible that the writer could finish and decrement @counter right here...
116
- @reader_mutex . synchronize do
117
- # So check again inside the synchronized section
118
- @reader_q . wait ( @reader_mutex ) if waiting_writer?
119
- end
116
+ @ReadLock . wait_until { !waiting_writer? }
120
117
121
118
# after a reader has waited once, they are allowed to "barge" ahead of waiting writers
122
119
# but if a writer is *running*, the reader still needs to wait (naturally)
123
- while ( true )
124
- c = @counter . value
120
+ while true
121
+ c = @Counter . value
125
122
if running_writer? ( c )
126
- @reader_mutex . synchronize do
127
- @reader_q . wait ( @reader_mutex ) if running_writer?
128
- end
123
+ @ReadLock . wait_until { !running_writer? }
129
124
else
130
- return if @counter . compare_and_swap ( c , c +1 )
125
+ return if @Counter . compare_and_swap ( c , c +1 )
131
126
end
132
127
end
133
128
else
134
- break if @counter . compare_and_swap ( c , c +1 )
129
+ break if @Counter . compare_and_swap ( c , c +1 )
135
130
end
136
- end
131
+ end
137
132
true
138
133
end
139
134
140
135
# Release a previously acquired read lock.
141
136
#
142
137
# @return [Boolean] true if the lock is successfully released
143
138
def release_read_lock
144
- while ( true )
145
- c = @counter . value
146
- if @counter . compare_and_swap ( c , c -1 )
139
+ while true
140
+ c = @Counter . value
141
+ if @Counter . compare_and_swap ( c , c -1 )
147
142
# If one or more writers were waiting, and we were the last reader, wake a writer up
148
143
if waiting_writer? ( c ) && running_readers ( c ) == 1
149
- @writer_mutex . synchronize { @writer_q . signal }
144
+ @WriteLock . signal
150
145
end
151
146
break
152
147
end
@@ -161,32 +156,31 @@ def release_read_lock
161
156
# @raise [Concurrent::ResourceLimitError] if the maximum number of writers
162
157
# is exceeded.
163
158
def acquire_write_lock
164
- while ( true )
165
- c = @counter . value
159
+ while true
160
+ c = @Counter . value
166
161
raise ResourceLimitError . new ( 'Too many writer threads' ) if max_writers? ( c )
167
162
168
163
if c == 0 # no readers OR writers running
169
164
# if we successfully swap the RUNNING_WRITER bit on, then we can go ahead
170
- break if @counter . compare_and_swap ( 0 , RUNNING_WRITER )
171
- elsif @counter . compare_and_swap ( c , c +WAITING_WRITER )
172
- while ( true )
165
+ break if @Counter . compare_and_swap ( 0 , RUNNING_WRITER )
166
+ elsif @Counter . compare_and_swap ( c , c +WAITING_WRITER )
167
+ while true
173
168
# Now we have successfully incremented, so no more readers will be able to increment
174
169
# (they will wait instead)
175
170
# However, readers OR writers could decrement right here, OR another writer could increment
176
- @writer_mutex . synchronize do
171
+ @WriteLock . wait_until do
177
172
# So we have to do another check inside the synchronized section
178
173
# If a writer OR reader is running, then go to sleep
179
- c = @counter . value
180
- @writer_q . wait ( @writer_mutex ) if running_writer? ( c ) || running_readers? ( c )
174
+ c = @Counter . value
175
+ ! running_writer? ( c ) && ! running_readers? ( c )
181
176
end
182
177
183
178
# We just came out of a wait
184
179
# If we successfully turn the RUNNING_WRITER bit on with an atomic swap,
185
180
# Then we are OK to stop waiting and go ahead
186
181
# Otherwise go back and wait again
187
- c = @counter . value
188
- break if !running_writer? ( c ) && !running_readers? ( c ) &&
189
- @counter . compare_and_swap ( c , c +RUNNING_WRITER -WAITING_WRITER )
182
+ c = @Counter . value
183
+ break if !running_writer? ( c ) && !running_readers? ( c ) && @Counter . compare_and_swap ( c , c +RUNNING_WRITER -WAITING_WRITER )
190
184
end
191
185
break
192
186
end
@@ -198,67 +192,60 @@ def acquire_write_lock
198
192
#
199
193
# @return [Boolean] true if the lock is successfully released
200
194
def release_write_lock
201
- while ( true )
202
- c = @counter . value
203
- if @counter . compare_and_swap ( c , c -RUNNING_WRITER )
204
- @reader_mutex . synchronize { @reader_q . broadcast }
205
- if waiting_writers ( c ) > 0 # if any writers are waiting...
206
- @writer_mutex . synchronize { @writer_q . signal }
207
- end
208
- break
209
- end
210
- end
195
+ c = @Counter . update { |c | c -RUNNING_WRITER }
196
+ @ReadLock . broadcast
197
+ @WriteLock . signal if waiting_writers ( c ) > 0
211
198
true
212
199
end
213
200
214
201
# Queries if the write lock is held by any thread.
215
202
#
216
203
# @return [Boolean] true if the write lock is held else false`
217
204
def write_locked?
218
- @counter . value >= RUNNING_WRITER
205
+ @Counter . value >= RUNNING_WRITER
219
206
end
220
207
221
208
# Queries whether any threads are waiting to acquire the read or write lock.
222
209
#
223
210
# @return [Boolean] true if any threads are waiting for a lock else false
224
211
def has_waiters?
225
- waiting_writer? ( @counter . value )
212
+ waiting_writer? ( @Counter . value )
226
213
end
227
214
228
215
private
229
216
230
217
# @!visibility private
231
- def running_readers ( c = @counter . value )
218
+ def running_readers ( c = @Counter . value )
232
219
c & MAX_READERS
233
220
end
234
221
235
222
# @!visibility private
236
- def running_readers? ( c = @counter . value )
223
+ def running_readers? ( c = @Counter . value )
237
224
( c & MAX_READERS ) > 0
238
225
end
239
226
240
227
# @!visibility private
241
- def running_writer? ( c = @counter . value )
228
+ def running_writer? ( c = @Counter . value )
242
229
c >= RUNNING_WRITER
243
230
end
244
231
245
232
# @!visibility private
246
- def waiting_writers ( c = @counter . value )
233
+ def waiting_writers ( c = @Counter . value )
247
234
( c & MAX_WRITERS ) / WAITING_WRITER
248
235
end
249
236
250
237
# @!visibility private
251
- def waiting_writer? ( c = @counter . value )
238
+ def waiting_writer? ( c = @Counter . value )
252
239
c >= WAITING_WRITER
253
240
end
254
241
255
242
# @!visibility private
256
- def max_readers? ( c = @counter . value )
243
+ def max_readers? ( c = @Counter . value )
257
244
( c & MAX_READERS ) == MAX_READERS
258
245
end
259
246
260
247
# @!visibility private
261
- def max_writers? ( c = @counter . value )
248
+ def max_writers? ( c = @Counter . value )
262
249
( c & MAX_WRITERS ) == MAX_WRITERS
263
250
end
264
251
end
0 commit comments