Skip to content

Commit 2859060

Browse files
committed
Merge pull request #259 from ruby-concurrency/read_write_lock
ReadWriteLock donated by @alexdowad
2 parents 235f061 + 3e1b83c commit 2859060

File tree

7 files changed

+957
-0
lines changed

7 files changed

+957
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* Deprecated all clock-time based timer scheduling
1616
- Only support scheduling by delay
1717
- Effects `Concurrent.timer`, `TimerSet`, and `ScheduledTask`
18+
* Added new `ReadWriteLock` class
1819
* Consistent `at_exit` behavior for Java and Ruby thread pools.
1920
* Added `at_exit` handler to Ruby thread pools (already in Java thread pools)
2021
- Ruby handler stores the object id and retrieves from `ObjectSpace`

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ This library contains a variety of concurrency abstractions at high and low leve
8989
* [M-Structures](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/MVar.html) (MVar)
9090
* [Thread-local variables](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadLocalVar.html)
9191
* [Software transactional memory](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/TVar.html) (TVar)
92+
* [ReadWriteLock](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ReadWriteLock.html)
9293

9394
## Usage
9495

examples/benchmark_read_write_lock.rb

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#!/usr/bin/env ruby
2+
3+
$:.push File.join(File.dirname(__FILE__), '../lib')
4+
5+
require 'concurrent/atomic/read_write_lock'
6+
require 'benchmark'
7+
require 'optparse'
8+
require 'ostruct'
9+
10+
$options = OpenStruct.new
11+
$options.threads = 100
12+
$options.interleave = false
13+
$options.compare = false
14+
15+
OptionParser.new do |opts|
16+
opts.banner = "Usage: #{File.basename(__FILE__)} [options]"
17+
18+
opts.on('-t', '--threads=THREADS', OptionParser::DecimalInteger, "Number of threads per test (default #{$options.threads})") do |value|
19+
$options.threads = value
20+
end
21+
22+
opts.on('-i', '--[no-]interleave', 'Interleave output to check for starvation') do |value|
23+
$options.interleave = value
24+
end
25+
26+
opts.on('-c', '--[no-]compare', 'Compare with other implementations') do |value|
27+
$options.compare = value
28+
end
29+
30+
opts.on('-h', '--help', 'Prints this help') do
31+
puts opts
32+
exit
33+
end
34+
end.parse!
35+
36+
def jruby?
37+
defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
38+
end
39+
40+
# for performance comparison with ReadWriteLock
41+
class SimpleMutex
42+
def initialize; @mutex = Mutex.new; end
43+
def with_read_lock
44+
@mutex.synchronize { yield }
45+
end
46+
alias :with_write_lock :with_read_lock
47+
end
48+
49+
# for seeing whether my correctness test is doing anything...
50+
# and for seeing how great the overhead of the test is
51+
# (apart from the cost of locking)
52+
class FreeAndEasy
53+
def with_read_lock
54+
yield # thread safety is for the birds... I prefer to live dangerously
55+
end
56+
alias :with_write_lock :with_read_lock
57+
end
58+
59+
if jruby?
60+
# the Java platform comes with a read-write lock implementation
61+
# performance is very close to ReadWriteLock, but just a *bit* slower
62+
require 'java'
63+
class JavaReadWriteLock
64+
def initialize
65+
@lock = java.util.concurrent.locks.ReentrantReadWriteLock.new
66+
end
67+
def with_read_lock
68+
@lock.read_lock.lock
69+
result = yield
70+
@lock.read_lock.unlock
71+
result
72+
end
73+
def with_write_lock
74+
@lock.write_lock.lock
75+
result = yield
76+
@lock.write_lock.unlock
77+
result
78+
end
79+
end
80+
end
81+
82+
def test(lock)
83+
puts "READ INTENSIVE (80% read, 20% write):"
84+
single_test(lock, ($options.threads * 0.8).floor, ($options.threads * 0.2).floor)
85+
puts "WRITE INTENSIVE (80% write, 20% read):"
86+
single_test(lock, ($options.threads * 0.2).floor, ($options.threads * 0.8).floor)
87+
puts "BALANCED (50% read, 50% write):"
88+
single_test(lock, ($options.threads * 0.5).floor, ($options.threads * 0.5).floor)
89+
end
90+
91+
def single_test(lock, n_readers, n_writers, reader_iterations=50, writer_iterations=50, reader_sleep=0.001, writer_sleep=0.001)
92+
puts "Testing #{lock.class} with #{n_readers} readers and #{n_writers} writers. Readers iterate #{reader_iterations} times, sleeping #{reader_sleep}s each time, writers iterate #{writer_iterations} times, sleeping #{writer_sleep}s each time"
93+
mutex = Mutex.new
94+
bad = false
95+
data = 0
96+
97+
result = Benchmark.measure do
98+
readers = n_readers.times.collect do
99+
Thread.new do
100+
reader_iterations.times do
101+
lock.with_read_lock do
102+
print "r" if $options.interleave
103+
mutex.synchronize { bad = true } if (data % 2) != 0
104+
sleep(reader_sleep)
105+
mutex.synchronize { bad = true } if (data % 2) != 0
106+
end
107+
end
108+
end
109+
end
110+
writers = n_writers.times.collect do
111+
Thread.new do
112+
writer_iterations.times do
113+
lock.with_write_lock do
114+
print "w" if $options.interleave
115+
# invariant: other threads should NEVER see "data" as an odd number
116+
value = (data += 1)
117+
# if a reader runs right now, this invariant will be violated
118+
sleep(writer_sleep)
119+
# this looks like a strange way to increment twice;
120+
# it's designed so that if 2 writers run at the same time, at least
121+
# one increment will be lost, and we can detect that at the end
122+
data = value+1
123+
end
124+
end
125+
end
126+
end
127+
128+
readers.each { |t| t.join }
129+
writers.each { |t| t.join }
130+
puts "BAD!!! Readers+writers overlapped!" if mutex.synchronize { bad }
131+
puts "BAD!!! Writers overlapped!" if data != (n_writers * writer_iterations * 2)
132+
end
133+
puts result
134+
end
135+
136+
test(Concurrent::ReadWriteLock.new)
137+
test(JavaReadWriteLock.new) if $options.compare && jruby?
138+
test(SimpleMutex.new) if $options.compare
139+
test(FreeAndEasy.new) if $options.compare

0 commit comments

Comments
 (0)