ruby-on-rails - 使用线程时处理 Rails 中的循环依赖

标签 ruby-on-rails ruby multithreading

以下是添加到 my app 的五个文件(无数据库,无需设置):

lib/tasks/precomputation.rake

namespace :precomputation do
  desc "This fetches data for precomputation"
  task fetch_all: :environment do
      Precomputation.precompute_all_data
    # end
  end
end

app/models/precomputation.rb

class Precomputation
  def self.precompute_all_data
    ad_accounts = [1,2,3]
    bgthread = BackgroundThread::BGThreadPool.new(1)
    tasks = []
    ad_accounts.each do |ad_account_id|
      p = Proc.new do
        begin
          MongoPipeline::FbAdCampaignMongoPipeline.new(ad_account_id).fetch_all
          false
        ensure
          GC.start
        end
      end
      tasks << [p, "test #{ad_account_id}"]
    end
    bgthread.add_randomized_tasks(tasks)
    bgthread.do_work
  end
end

应用程序/模型/mongo_pipeline.rb

module MongoPipeline
  class Base
    def initialize(ad_account_id)
    end

    def insert_data
      puts 'inserting data'
    end

    def fetch_all
      extract_data # Child Class defines this method
      insert_data # Base class defines this method
    end
  end
end

app/models/mongo_pipeline/fb_ad_campaign_mongo_pipeline.rb

module MongoPipeline
  class FbAdCampaignMongoPipeline < MongoPipeline::Base
    def extract_data
      puts 'here is campaign data'
    end
  end
end

和app/models/background_thread.rb

(注意:使用并行 gem 且无后台线程的替代实现也会因类似错误而崩溃 -: https://github.com/pratik60/circular_dependency_havoc/tree/parallel )

错误日志

rake aborted!
Circular dependency detected while autoloading constant MongoPipeline
/Users/pratikbothra/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.4/lib/active_support/dependencies.rb:492:in `load_missing_constant'
/Users/pratikbothra/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.4/lib/active_support/dependencies.rb:184:in `const_missing'
/Users/pratikbothra/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.4/lib/active_support/dependencies.rb:526:in `load_missing_constant'
/Users/pratikbothra/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.4/lib/active_support/dependencies.rb:184:in `const_missing'
/webapps/circular_dependency_havoc/app/models/precomputation.rb:9:in `block (2 levels) in precompute_all_data'
/webapps/circular_dependency_havoc/app/models/background_thread.rb:91:in `call'
/webapps/circular_dependency_havoc/app/models/background_thread.rb:91:in `block in background'
Tasks: TOP => precomputation:fetch_all
(See full trace by running task with --trace)

关于我做错了什么有什么想法吗?后台线程库被克隆并刚刚修改。如果您认为这是问题所在,请随时更换它。任何建议、任何想法都非常受欢迎。

最佳答案

我在 rake 任务中构建线程循环来轮询外部服务器时遇到了这个问题,并得出了相同的结论,即它与自动加载和不可预测的竞争条件相关。然而,我不想简单地打开急切加载,因此我有两种方法来解决这个问题。一是弄清楚哪些类正在我的任务中加载,并将它们加载到父级中,然后再生成任何线程:

# preload classes that showed circular dependencies just in this task
MediaFile
VideoPlatformIntegration
Tag

10.times.map{Thread.new{...}}

这就像一个魅力,但放入生产代码中有点奇怪,并且对于任务中的任何更改或其调用的任何方法都不稳健。

另一个更不完美,但实际上只是修改开发环境中的行为,而不依赖于了解可能导致这种情况的一切。由于它还处于开发阶段,我认为这是可以接受的:在每个初始线程开始时休眠一段随机时间(0 到 1 秒之间):

sleep(Random.rand) if Rails.env == "development"

这种 sleep 可以防止线程同时启动,并大大减少加载类时出现任何竞争条件的可能性。我在这里使用一批简单的 10 个线程进行了概念验证,在 sleep 时运行脚本 4 次,在不 sleep 时运行脚本 4 次,在不 sleep 时显示可靠的循环依赖错误:

[video_platform_integration] andrew@~/3p/app3$ rails runner '10.times.map{Thread.new{MediaFile.last rescue puts($!)}}'
Circular dependency detected while autoloading constant MediaFile
Circular dependency detected while autoloading constant MediaFile
[video_platform_integration] andrew@~/3p/app3$ rails runner '10.times.map{Thread.new{MediaFile.last rescue puts($!)}}'
Circular dependency detected while autoloading constant MediaFile
[video_platform_integration] andrew@~/3p/app3$ rails runner '10.times.map{Thread.new{MediaFile.last rescue puts($!)}}'
[video_platform_integration] andrew@~/3p/app3$ rails runner '10.times.map{Thread.new{MediaFile.last rescue puts($!)}}'
Circular dependency detected while autoloading constant MediaFile
Circular dependency detected while autoloading constant MediaFile
[video_platform_integration] andrew@~/3p/app3$ rails runner '10.times.map{Thread.new{sleep(Random.rand); MediaFile.last rescue puts($!)}}'
[video_platform_integration] andrew@~/3p/app3$ rails runner '10.times.map{Thread.new{sleep(Random.rand); MediaFile.last rescue puts($!)}}'
[video_platform_integration] andrew@~/3p/app3$ rails runner '10.times.map{Thread.new{sleep(Random.rand); MediaFile.last rescue puts($!)}}'
[video_platform_integration] andrew@~/3p/app3$ rails runner '10.times.map{Thread.new{sleep(Random.rand); MediaFile.last rescue puts($!)}}'

关于ruby-on-rails - 使用线程时处理 Rails 中的循环依赖,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36382752/

相关文章:

ruby-on-rails - 如何在 rake 任务中使用救援

mysql - 任何不在本地安装mysql服务器的安装mysql2 GEM的方法

ruby Dir.entries 不识别特殊字符

ruby-on-rails - Rails 如何从字符串地址获取经纬度并将其放在谷歌地图上?

java - 这是在 Java 中使用同步的正确方法吗?

mysql - 迁移以创建表引发 Mysql2::Error: 表不存在

ruby-on-rails - 显示日期时间,如 1 小时前、5 小时前、3 天前、

ruby-on-rails - Rails - 使用 paypal-recurring gem 处理 PayPal IPN 回调

c - 这个C多线程tcp服务器正确吗?

c# - 什么是生活模拟器的好架构