用户在Heroku上的Thin,Sinatra应用程序中的线程

fok*_*oki 5 ruby multithreading heroku sinatra

我有一组耗时的操作,这些操作特定于我的应用程序的每个用户,它都被封装在一个方法(例如write_collections方法)中.在这个方法中,程序与Facebook和MongoDB进行通信.我想在每个用户的线程中运行此方法.

    在get '/'Sinatra路由中调用此线程,但仅需要线程(数据库中的状态)的结果get '/calculate'.我的想法是运行线程get '/'并加入它get '/calculate'以确保在计算用户启动结果之前,所有用户的数据都已在数据库中正确写入.

为了显示:


方法我

get "/" do    
  @user = @graph.get_object("me")

  data_thread = Thread.new do
    write_collections(@user)
  end

  session[:data_thread] = data_thread.object_id

  erb :index
end

get "/calculate" do
  begin
  # Is this safe enough?
  if ObjectSpace._id2ref(session[:data_thread]).alive?
      data_thread = ObjectSpace._id2ref(session[:data_thread])
      data_thread.join
  end
  rescue RangeError => range_err
    # session[:data_thread] is not id value
    # direct access to /calculate without session
  rescue TypeError => type_err
    # session[:data_thread] is nil
  end

  # do calculations based on state in database
  # and show results to user

  "<p>Under construction</p>"
end
Run Code Online (Sandbox Code Playgroud)

    要找到特定用户应该等待加入的正确线程,我当前正在使用ObjectSpace._id2ref(session[:data_thread]).

  • 它足够安全吗?

详细:

从官方Ruby文档Object#object_id:

object_id→fixnum:返回obj的整数标识符.对于给定对象的所有id调用都将返回相同的数字,并且没有两个活动对象将共享id.

并为ObjectSpace:

ObjectSpace模块包含许多与垃圾收集工具交互的例程,允许您使用迭代器遍历所有活动对象.

  • 第一个引用中的"活动对象"与第二个引用中的"活动对象"相同吗?

    我们假设以下情况:

  1. 用户A访问'/'[现在一个线程以object_id a启动]
  2. 线程A已完成[它不再处于活动状态且它已object_id被释放]
  3. 用户B访问'/'[现在B线程以相同的object_id a启动(*可能吗?)]
  4. 用户访问'/calculate'[ session[:data_thread]一个这样ObjectSpace._id2ref(session[:data_thread])实际上是线程.]
  5. 不一致的状态-用户一个等待线程.

    • 这种情况在Sinatra,Thin,Heroku中是否可行?

方法二

configure do
  # map user_id to corresponding user's thread
  data_threads_hash = {}
  set :data_threads_hash, data_threads_hash
end

get "/" do    
  @user = @graph.get_object("me")

  data_thread = Thread.new do
    write_collections(@user)
  end

  session[:user_id] = @user['id']
  settings.data_threads_hash[session[:user_id]] = data_thread

  erb :index
end

get "/calculate" do

  if settings.data_threads_hash[session[:user_id]].alive?
    data_thread = settings.data_threads_hash[session[:user_id]]
    data_thread.join
    settings.data_threads_hash.delete session[:user_id]
  end

  # do calculations based on state in database
  # and show results to user

  "<p>Under construction</p>"

end
Run Code Online (Sandbox Code Playgroud)

详细:

我在阅读Sinatra之后试过这个:自述文件.在配置下:

在启动时,在任何环境中运行一次......您可以通过设置访问这些选项...

Scopes和Binding,Application/Class Scope下:

每个Sinatra应用程序都对应于Sinatra :: Base的子类.如果您使用顶级DSL(需要'sinatra'),那么这个类是Sinatra :: Application,否则它是您明确创建的子类.在类级别,您有get或之前的方法,但您无法访问请求或会话对象,因为所有请求只有一个应用程序类.

我正在使用顶级DSL.

通过set创建的选项是类级别的方法...您可以像这样访问范围对象(类):来自请求范围内的设置

  • 请记住@FrederickCheung在Scopes和Binding的评论和引用中所说的话,如果我需要一个以上的dyno/worker(目前这个应用只使用一个dyno),这种方法是否有用?

摘要

  • 我应该如何处理Sinatra中用户及其相应线程的描述情况,上述示例的方法有多好或多坏?

欢迎任何评论或参考.

B S*_*ven 2

我不确定这个设计是否有意义。为什么每个用户需要自己的线程?为什么一个请求会加入另一个请求创建的线程?即使这是可能的(仅使用一个测功机),我也不认为这是做你想做的事情的好方法。

根据问题中应用程序的描述,您希望在方法完成calculate后运行某些方法write_collections。那么,为什么该方法不能write_collections调用某些calculate方法呢?或者,为什么不能使用后过滤器或观察器来进行计算?

更一般地说,您似乎混淆了两个单独的功能:

  1. 功能calculate
  2. GET /calculate

我认为应该只有一个触发器来调用该calculate功能。它是在完成时write_collections 在用户请求时 ( GET /calculate)。

更通用的解决方案是让该calculate功能在后台运行,并将结果保存到数据库中。稍后,当用户发出请求时,它就准备好了并且可以快速返回。