103 lines
2.1 KiB
Crystal
103 lines
2.1 KiB
Crystal
require "wait_group"
|
|
|
|
class Buckets
|
|
@buckets : Array(Int32)
|
|
@locks : Array(Mutex)
|
|
|
|
def initialize (n)
|
|
@buckets = Array.new(n, 100)
|
|
@locks = Array.new(n) { Mutex.new }
|
|
end
|
|
|
|
def size
|
|
@buckets.size
|
|
end
|
|
|
|
def [] (idx)
|
|
@locks[idx].synchronize do
|
|
@buckets[idx]
|
|
end
|
|
end
|
|
|
|
def transfer (from, to, amount)
|
|
return if from == to
|
|
from, to, amount = to, from, amount.abs if amount < 0
|
|
l1, l2 = from, to
|
|
l1, l2 = l2, l1 if l1 > l2
|
|
@locks[l1].synchronize do
|
|
@locks[l2].synchronize do
|
|
amount = Math.min(@buckets[from], amount)
|
|
@buckets[from] -= amount
|
|
@buckets[to] += amount
|
|
end
|
|
end
|
|
end
|
|
|
|
def snapshot
|
|
(0...@buckets.size).each do |i| @locks[i].lock end
|
|
result = @buckets.dup
|
|
(0...@buckets.size).reverse_each do |i| @locks[i].unlock end
|
|
result
|
|
end
|
|
end
|
|
|
|
# pick two buckets and make their values closer to equal
|
|
def equalize (buckets, channel)
|
|
loop do
|
|
b1 = rand(buckets.size)
|
|
b2 = rand(buckets.size)
|
|
diff = buckets[b1] - (buckets[b1] + buckets[b2]) // 2
|
|
buckets.transfer(b1, b2, diff)
|
|
# check termination
|
|
select
|
|
when stop = channel.receive
|
|
return
|
|
else # continue
|
|
end
|
|
end
|
|
end
|
|
|
|
# pick two buckets and arbitrarily redistribute their values
|
|
def distribute (buckets, channel)
|
|
loop do
|
|
b1 = rand(buckets.size)
|
|
b2 = rand(buckets.size)
|
|
buckets.transfer(b1, b2, rand(0..buckets[b1]))
|
|
#check termination
|
|
select
|
|
when stop = channel.receive
|
|
return
|
|
else # continue
|
|
end
|
|
end
|
|
end
|
|
|
|
# display contents of buckets and sum
|
|
def display (buckets, channel)
|
|
loop do
|
|
bs = buckets.snapshot
|
|
puts "Total: %4d, buckets: %s" % { bs.sum, bs }
|
|
sleep 1.second
|
|
#check termination
|
|
select
|
|
when stop = channel.receive
|
|
return
|
|
else # continue
|
|
end
|
|
end
|
|
end
|
|
|
|
wg = WaitGroup.new
|
|
stop_channels = Array.new(3) { Channel(Bool).new }
|
|
|
|
buckets = Buckets.new(10)
|
|
|
|
wg.spawn { display(buckets, stop_channels[0]) }
|
|
wg.spawn { equalize(buckets, stop_channels[1]) }
|
|
wg.spawn { distribute(buckets, stop_channels[2]) }
|
|
|
|
sleep 10.seconds
|
|
|
|
stop_channels.each do |ch| ch.send(true) end
|
|
|
|
wg.wait
|