13

我必须编写一个线程化的 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 操作,两者是否会设置相同的 @current_user 变量?即第一个请求在处理 .html.erb 文件时会发现它的@current_user 实例变量突然被第二个线程更改了吗?

谢谢

4

2 回答 2

14

每个请求都会获得一个新的控制器实例。因此,控制器实例变量是线程安全的。params并且session还由控制器实例变量(或请求对象本身)支持,因此也是安全的。

于 2012-08-17T10:25:30.017 回答
3

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

现在回到你的具体例子。两个请求同时命中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) 的约束。

于 2012-08-17T10:36:56.350 回答