Skip to content

Commit b2ad02b

Browse files
committed
TVar: experiment.
1 parent bda33a6 commit b2ad02b

File tree

2 files changed

+180
-5
lines changed

2 files changed

+180
-5
lines changed

demos/tvar-demo.rb

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def grand_total
117117

118118
GRAND_TOTAL = ACCOUNT_TOTALS.inject(0, :+)
119119

120-
TRANSFERS = (0..10_000_000).map do
120+
TRANSFERS = (0..1_000_000).map do
121121
Transfer.new(
122122
RANDOM.rand(ACCOUNT_TOTALS.size),
123123
RANDOM.rand(ACCOUNT_TOTALS.size),
@@ -133,18 +133,29 @@ def test(bank_class)
133133

134134
puts "total before: #{bank.grand_total}"
135135

136-
start = Time.now
136+
start_barrier = Concurrent::CountDownLatch.new(THREADS)
137+
finish_barrier = Concurrent::CountDownLatch.new(THREADS)
137138

138-
(1..THREADS).map { |n|
139+
(1..THREADS).each do |n|
139140
Thread.new do
141+
start_barrier.count_down
142+
start_barrier.wait
143+
140144
TRANSFERS[(n*TRANSFER_PER_THREAD)..((n+1)*TRANSFER_PER_THREAD)].each do |transfer|
141145
bank.transfer(transfer.from, transfer.to, transfer.sum)
142146
end
147+
148+
finish_barrier.count_down
143149
end
144-
}.each(&:join)
150+
end
151+
152+
start = Time.now
153+
start_barrier.wait
154+
finish_barrier.wait
155+
time = Time.new - start
145156

146157
puts "total after: #{bank.grand_total}"
147-
puts "took #{Time.now - start}s"
158+
puts "took #{time}s"
148159
end
149160

150161
test UnsynchronizedBank

experiments/tvar/bank-accounts.rb

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
require 'monitor'
2+
require 'concurrent'
3+
4+
class UnsynchronizedBank
5+
6+
def initialize(account_totals)
7+
@accounts = account_totals.dup
8+
end
9+
10+
def transfer(from, to, sum)
11+
if @accounts[from] < sum
12+
false
13+
else
14+
@accounts[from] -= sum
15+
@accounts[to] += sum
16+
true
17+
end
18+
end
19+
20+
def grand_total
21+
@accounts.inject(0, :+)
22+
end
23+
24+
end
25+
26+
class CoarseLockBank
27+
28+
def initialize(account_totals)
29+
@accounts = account_totals.dup
30+
@lock = Mutex.new
31+
end
32+
33+
def transfer(from, to, sum)
34+
@lock.synchronize do
35+
if @accounts[from] < sum
36+
false
37+
else
38+
@accounts[from] -= sum
39+
@accounts[to] += sum
40+
true
41+
end
42+
end
43+
end
44+
45+
def grand_total
46+
@accounts.inject(0, :+)
47+
end
48+
49+
end
50+
51+
class FineLockBank
52+
53+
Account = Struct.new(:lock, :value)
54+
55+
def initialize(account_totals)
56+
@accounts = account_totals.map do |v|
57+
Account.new(Monitor.new, v)
58+
end
59+
end
60+
61+
def transfer(from, to, sum)
62+
locks = [@accounts[from].lock, @accounts[to].lock]
63+
ordered_locks = locks.sort{ |a, b| a.object_id <=> b.object_id }
64+
65+
ordered_locks[0].synchronize do
66+
ordered_locks[1].synchronize do
67+
if @accounts[from].value < sum
68+
false
69+
else
70+
@accounts[from].value -= sum
71+
@accounts[to].value += sum
72+
true
73+
end
74+
end
75+
end
76+
end
77+
78+
def grand_total
79+
@accounts.map(&:value).inject(0, :+)
80+
end
81+
82+
end
83+
84+
class TransactionalBank
85+
86+
def initialize(account_totals)
87+
@accounts = account_totals.map do |v|
88+
Concurrent::TVar.new(v)
89+
end
90+
end
91+
92+
def transfer(from, to, sum)
93+
Concurrent::atomically do
94+
if @accounts[from].value < sum
95+
false
96+
else
97+
@accounts[from].value -= sum
98+
@accounts[to].value += sum
99+
true
100+
end
101+
end
102+
end
103+
104+
def grand_total
105+
@accounts.map(&:value).inject(0, :+)
106+
end
107+
108+
end
109+
110+
RANDOM = Random.new(0)
111+
112+
Transfer = Struct.new(:from, :to, :sum)
113+
114+
ACCOUNT_TOTALS = (0..100_000).map do
115+
RANDOM.rand(100)
116+
end
117+
118+
GRAND_TOTAL = ACCOUNT_TOTALS.inject(0, :+)
119+
120+
TRANSFERS = (0..1_000_000).map do
121+
Transfer.new(
122+
RANDOM.rand(ACCOUNT_TOTALS.size),
123+
RANDOM.rand(ACCOUNT_TOTALS.size),
124+
RANDOM.rand(100))
125+
end
126+
127+
def test(bank_class)
128+
(1..8).each do |threads|
129+
transfer_per_thread = TRANSFERS.size / threads
130+
131+
bank = bank_class.new(ACCOUNT_TOTALS)
132+
total_before = bank.grand_total
133+
134+
start_barrier = Concurrent::CountDownLatch.new(threads)
135+
finish_barrier = Concurrent::CountDownLatch.new(threads)
136+
137+
(1..threads).each do |n|
138+
Thread.new do
139+
start_barrier.count_down
140+
start_barrier.wait
141+
142+
TRANSFERS[(n*transfer_per_thread)..((n+1)*transfer_per_thread)].each do |transfer|
143+
bank.transfer(transfer.from, transfer.to, transfer.sum)
144+
end
145+
146+
finish_barrier.count_down
147+
end
148+
end
149+
150+
start = Time.now
151+
start_barrier.wait
152+
finish_barrier.wait
153+
time = Time.new - start
154+
155+
raise "error" unless bank.grand_total == total_before or bank_class == UnsynchronizedBank
156+
157+
puts "#{bank_class} #{threads} #{time}"
158+
end
159+
end
160+
161+
test UnsynchronizedBank
162+
test CoarseLockBank
163+
test FineLockBank
164+
test TransactionalBank

0 commit comments

Comments
 (0)