实现可以跨多个线程修改但锁数最少的哈希的最佳方法是什么.出于这个问题的目的,您可以假设散列将被重读.它在所有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

Ruby相关问答推荐

Ruby - 使用索引值识别和更新数组中的重复项

这个#divmod 方法输出这个结果是做什么的?

每次调用返回新的 REST 响应的 Ruby Rspec class_double

应用 ransack 日期字段过滤器后,相同的输出票证重复而不是一张

Ruby 3 从多个预定纤程中收集结果

如何在 Shopify 脚本编辑器中显示数组值?

使用 Drive API 创建空文件

each_with_index_do 从 1 开始索引

在 Ruby 中,如何确定字符串是否不在数组中?

为什么 Ruby 中的 `a = a` `nil`?

如何将两周时间添加到 Time.now?

如何在 Ruby 中使用 (n) 诅咒?

如何在 Ruby 中临时重定向标准错误?

你能在 Python 的核心类型上修改补丁方法吗?

Ruby 模板:如何将变量传递到内联 ERB?

判断是否在 SASS 中定义了变量

在 Ruby 脚本中解析命令行参数

在 Ruby 中获取用户主目录的跨平台方法?

给定一组参数,如何将这些参数发送到 Ruby 中的特定函数?

如何使用 Ruby 删除回车?