ruby - 纯 Ruby 并发哈希

标签 ruby concurrency hash locking

实现可跨多个线程修改但使用最少锁数的哈希的最佳方法是什么。出于这个问题的目的,您可以假设哈希将是重读的。它在所有 Ruby 实现中都必须是线程安全的,包括那些以真正同步的方式运行的实现,例如 JRuby,并且它必须用纯 Ruby 编写(不允许使用 C 或 Java)。

请随意提交一个总是锁定的简单解决方案,但这不太可能是最佳解决方案。优雅点,但锁定的可能性较小胜过较小的代码。

最佳答案

好的,现在您已经指定了“线程安全”的实际含义,下面是两个可能的实现。以下代码将在 MRI 和 JRuby 中永远运行。无锁实现遵循最终一致性模型,如果主线程不断变化,每个线程都使用它自己的哈希 View 。需要一些小技巧来确保将所有信息存储在线程中不会泄漏内存,但这已经过处理和测试——运行此代码时进程大小不会增加。这两种实现都需要更多的工作才能“完成”,这意味着删除、更新等需要一些思考,但以下两个概念中的任何一个都可以满足您的要求。

对于阅读此主题的人来说,重要的是要意识到整个问题是 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 并发哈希,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1080993/

相关文章:

Regex/Ruby - 分割保留分隔符

java - 为缓存构建一个对象, "asynchronously"(有点),用Java : howto?

php - 为什么md5仍然被广泛使用

ruby-on-rails - 如何通过 ruby​​ 中的位置获取哈希值?

arrays - 哈希键仅存储循环的最后一个元素

javascript - 如何访问只能部分访问的 Rails 变量?

ruby - 使用 rspec 的 Selenium 屏幕截图

java - 使用 spring 入站文件适配器进行并发处理

go - golang 是否可以接受优雅的关闭写入并发模式?

sql - 如何查看 SQL ActiveRecord 生成的内容?