multithreading - Rails 4.2自动加载不是线程安全的

标签 multithreading ruby-on-rails-4 eager-loading

我有以下模型:

class User < ActiveRecord::Base
  def send_message(content)
    MessagePoro.new(content).deliver!
  end

  def self.send_to_all(content)
    threads = []
    all.each do |user|
      threads << Thread.new do
        user.send_message(content)
      end
    end
    threads.each(&:join)
  end
end

MessagePoro 模型可以很简单,例如应用程序/模型/message_poro.rb:
class MessagePoro
  def initialize(content)
    # ...
  end

  def deliver!
    # ...
  end
end

现在,当我有例如100 个用户,我正在运行 User.send_to_all("test") 我是 有时得到那些错误:
RuntimeError: Circular dependency detected while autoloading constant MessagePoro

或者:
wrong number of arguments (1 for 0)

我想这一定是因为没有加载 MessagePoro 并且所有线程都尝试同时加载它,或者类似的东西。由于这些错误仅在某些时候发生,我很确定只有在存在“竞争条件”或与线程有关的情况下才会发生。我曾尝试在启动 Threads 之前初始化 MessagePoro,并且我玩过 eager_loading,但问题似乎仍然存在。
我还能尝试什么来缓解这个问题?

最佳答案

我最近在尝试使用放置在 [rails_root]/lib 中的额外自定义库时遇到了一个非常相似的问题。目录。

TL;博士:

您可以使用急切加载来解决此问题,因为这样可以确保所有常量/模块/类在任何实际代码运行之前都在内存中。然而,为了这个工作:

  • 您必须拥有 config.eager_load = true在 Rails 配置中设置(在生产环境中默认设置)
  • 你的class-to-be-eager-loaded所在的文件必须在config.eager_load_paths ,而不是 config.autoload_paths .

  • 或者

    您可以使用 requirerequire_dependency (另一个 ActiveSupport 功能)以确保在 Rails 自动加载之前显式加载您需要的代码。

    更多信息

    正如 digidigo 在他的回复中提到的,循环依赖错误来自 ActiveSupport::Dependencies模块,或 Rails 自动加载器 更笼统地说。此代码为 不是线程安全的 ,因为它使用该类/模块变量来存储它正在加载的文件。如果两个线程最终同时自动加载相同的东西,其中一个线程可能会因为看到要在该类变量中加载的文件并引发“循环依赖”错误而被误导。

    我在使用(线程)Puma 网络服务器在生产模式下运行 Rails 时遇到了这个问题。我们在 lib 中添加了一个小型库。在我们的 Rails 根目录中,最初添加了 libconfig.autoload_once_paths .在开发中一切都很好,但在生产中(启用 config.eager_loadconfig.cache_classes),我们偶尔会在几乎同时请求的情况下遇到这些相同的循环依赖问题。几个小时后的调试,当我围绕循环依赖单步执行 ActiveSupport 代码并看到不同线程在代码中的不同点启动时,我最终看到了在我眼前发生的非线程安全。第一个线程会将要加载的文件添加到 loading数组,然后第二个线程会在那里找到它并引发循环依赖错误。

    结果是向 autoload_paths 添加了一些东西或 autoload_once_paths也不意味着它会被急切的加载拾取。然而,情况恰恰相反——添加到 eager_load_paths 的路径如果 eager_load 被禁用,将考虑自动加载(参见 this article 了解更多信息)。我们切换到 eager_load_paths并且到目前为止没有进一步的问题。

    有趣的是,就在 Rails 4 测试版之前,生产环境中默认禁用自动加载,这意味着像这样的问题会导致 100% 的时间发生硬故障,而不是 5% 的时间导致古怪的线程失败.然而,这在 4.0 beta 版本中被及时恢复了——你可以看到一些关于它的(热情的)讨论 here (包括选择短语“老实说,你是在告诉我自己去他妈的?”)。不过,从那时起,该恢复已在 Rails 5.0.0beta1 之前恢复,因此希望将来有更少的人需要再次处理这个令人头疼的问题。

    额外说明:

    Rails 自动加载器与 Ruby 自动加载器完全分离——这似乎是因为 Rails 在尝试自动加载常量时对目录结构进行了更多推断。

    自 Ruby 2.0 起,Ruby 的自动加载似乎已成为线程安全的,但这与 Rails 自动加载代码无关。 Rails 的自动加载器似乎肯定是 不是 线程安全,如前所述。

    关于multithreading - Rails 4.2自动加载不是线程安全的,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29439323/

    相关文章:

    java - 这段代码多线程安全吗?

    multithreading - 交换的替代版本!还返回换出的值(value)

    sql - 查找 child 最多的家长记录(有很多关系)

    ruby-on-rails - rspec/guard 运行 minitest 并获取无效选项 : -f

    java - DefaultClientConnection 自动关闭

    java - Java JVM 是否保证在调用 "wait"时会通知正确的 "notify"?

    ruby-on-rails - Rails 4 和 Heroku - 抽取 Assets :precompile trying to call Postgres

    php - 带有约束的预加载会生成太多查询。我该如何缓解呢?

    c# - IRepository 和工作单元与预加载

    c# - 在 NHibernate 中急切加载子集合和子集合