实现可以跨多个线程修改但锁数最少的哈希的最佳方法是什么.出于这个问题的目的,您可以假设散列将被重读.它在所有Ruby实现中都必须是线程安全的,包括以真正同步方式运行的实现,比如JRuby,并且必须用纯Ruby编写(不允许使用C或Java).
请随意提交一个总是锁定的天真解决方案,但这不太可能是最好的解决方案.这意味着优雅,但与较小的代码相比,锁定的可能性较小.
实现可以跨多个线程修改但锁数最少的哈希的最佳方法是什么.出于这个问题的目的,您可以假设散列将被重读.它在所有Ruby实现中都必须是线程安全的,包括以真正同步方式运行的实现,比如JRuby,并且必须用纯Ruby编写(不允许使用C或Java).
请随意提交一个总是锁定的天真解决方案,但这不太可能是最好的解决方案.这意味着优雅,但与较小的代码相比,锁定的可能性较小.
好了,既然您指定了"线程安全"的实际含义,下面是两个可能的实现.以下代码将在MRI和JRuby中永远运行.无锁实现遵循最终的一致性模型,在该模型中,如果主线程处于流动状态,每个线程都使用自己的哈希视图.要确保在线程中存储所有信息不会泄漏内存,需要一些技巧,但这是经过处理和测试的―运行此代码时,进程大小不会增加.两种实现都需要更多的工作才能"完成",这意味着删除、更新等需要一些思考,但以下两个概念中的任何一个都将满足您的要求.
阅读本文的人必须意识到整个问题是JRuby独有的——在MRI中,内置哈希就足够了.
module Cash
def Cash.new(*args, &block)
env = ENV['CASH_IMPL']
impl = env ? Cash.const_get(env) : LocklessImpl
klass = defined?(JRUBY_VERSION) ? impl : ::Hash
klass.new(*args)
end
class LocklessImpl
def initialize
@hash = {}
end
def thread_hash
thread = Thread.current
thread[:cash] ||= {}
hash = thread[:cash][thread_key]
if hash
hash
else
hash = thread[:cash][thread_key] = {}
ObjectSpace.define_finalizer(self){ thread[:cash].delete(thread_key) }
hash
end
end
def thread_key
[Thread.current.object_id, object_id]
end
def []=(key, val)
time = Time.now.to_f
tuple = [time, val]
@hash[key] = tuple
thread_hash[key] = tuple
val
end
def [](key)
# check the master value
#
val = @hash[key]
# someone else is either writing the key or it has never been set. we
# need to invalidate our own copy in either case
#
if val.nil?
thread_val = thread_hash.delete(key)
return(thread_val ? thread_val.last : nil)
end
# check our own thread local value
#
thread_val = thread_hash[key]
# in this case someone else has written a value that we have never seen so
# simply return it
#
if thread_val.nil?
return(val.last)
end
# in this case there is a master *and* a thread local value, if the master
# is newer juke our own cached copy
#
if val.first > thread_val.first
thread_hash.delete(key)
return val.last
else
return thread_val.last
end
end
end
class LockingImpl < ::Hash
require 'sync'
def initialize(*args, &block)
super
ensure
extend Sync_m
end
def sync(*args, &block)
sync_synchronize(*args, &block)
end
def [](key)
sync(:SH){ super }
end
def []=(key, val)
sync(:EX){ super }
end
end
end
if $0 == __FILE__
iteration = 0
loop do
n = 42
hash = Cash.new
threads =
Array.new(10) {
Thread.new do
Thread.current.abort_on_exception = true
n.times do |key|
hash[key] = key
raise "#{ key }=nil" if hash[key].nil?
end
end
}
threads.map{|thread| thread.join}
puts "THREADSAFE: #{ iteration += 1 }"
end
end