【问题标题】:Thread-safe Rails controller actions - setting instance variables?线程安全的 Rails 控制器操作 - 设置实例变量?
【发布时间】:2012-08-17 10:04:01
【问题描述】:

我必须编写一个线程化的 Rails 应用程序,因为我在 Neo4j.rb 上运行它,它在 Rails 进程中嵌入了一个 Neo4j 图形数据库,因此我必须处理来自同一进程的多个请求。是的,如果连接到 Neo4j 数据库像 SQL 数据库一样工作,那就太酷了,但事实并非如此,所以我不再抱怨,直接使用它。

我非常担心编写并发代码的影响(我应该这样做),并且只需要一些关于如何处理常见场景的建议 - 控制器在会话哈希中设置实例变量或变量,然后发生了一些事情。考虑以下粗略的代码来说明我的意思:

# THIS IS NOT REAL PRODUCTION CODE
# I don't do this in real life, it is just to help me ask my question, I
# know about one-way hashing, etc.!

class SessionsController
  def create
    user = User.find_by_email_and_password(params[:email], params[:password])
    raise 'auth error' unless user
    session[:current_user_id] = user.id
    redirect_to :controller => 'current_user', :action => 'show'
  end
end

class CurrentUserController
  def show
    @current_user = User.find(session[:current_user_id])
    render :action => :show # .html.erb file that uses @current_user
  end
end

问题:这段代码中是否存在竞争条件?

在 SessionsController 中,session 哈希和 params 哈希是线程本地的吗?假设同一个浏览器会话使用不同的凭据向 /sessions#create 发出多个请求(借用 Rails 路由语法),登录的用户应该是最后击中session[:current_user_id] = user.id 行的请求?或者我应该在控制器动作周围加上一个互斥锁?

在 CurrentUserController 中,如果 show action 被两个不同会话的请求同时触发,是否会同时设置相同的 @current_user 变量? IE。第一个请求在处理 .html.erb 文件时会发现它的 @current_user 实例变量突然被第二个线程更改了吗?

谢谢

【问题讨论】:

    标签: ruby-on-rails thread-safety


    【解决方案1】:

    每个请求都会获得一个new instance of your controller。因此,控制器实例变量是线程安全的。 paramssession 也由控制器实例变量(或请求对象本身)支持,因此也是安全的。

    【讨论】:

    • 这是一个旧线程,抱歉。但我有一个跟进。这很有意义(以及为什么人们不担心控制器方法中的@variables。但是,如果这是真的,我很困惑这个人每个人的计数器都有问题tenderlovemaking.com/2012/06/18/removing-config-threadsafe.html
    • @OliverShaw 在那个 post 计数器中是一个类实例变量(因为它在 class << self 块中),所以所有请求都将修改同一个计数器
    • 回到 Ruby。我记得类变量,但不记得类实例变量。谢谢!
    【解决方案2】:

    了解线程之间共享的内容和不共享的内容很重要。

    现在回到您的具体示例。两个请求同时命中CurrentUserController#show,因此它们由两个并发线程处理。这里的关键是每个线程都有自己的CurrentUserController 实例,因此有两个@current_user 变量不会相互干扰。所以@current_user 周围没有竞争条件。

    竞态条件的一个例子是这样的:

    class ApplicationController < ActionController::Base
      before_each :set_current_user
      cattr_accessor :current_user
    
      def set_current_user
        self.class.current_user = User.find_by_id(session[:current_user_id])
      end
    end
    
    # model
    class LogMessage < ActiveRecord::Base
      belongs_to :user
    
      def self.log_action(attrs)
        log_message = new(attrs)
        log_message.user = ApplicationController.current_user
        log_message.save
      end
    end
    

    一般来说,由于 GIL(全局解释器锁),在 MRI ruby​​ 中使用线程的好处是相当有限的。有一些实现不受 GIL (jruby) 的约束。

    【讨论】:

    • 感谢您的示例,但是我已经知道类变量会导致竞争。在 GIL 问题上,我认为你错了 - Ruby 1.9 线程是原生的,而且我还是在使用 JRuby。
    • 我很高兴你在正确的道路上。至于 MRI ruby​​ 1.9:它有本地线程和 GIL,这是那些本地线程使用的全局互斥锁。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-06
    相关资源
    最近更新 更多