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

时间:2015-01-03 15:30:14

标签: ruby multithreading heroku sinatra

我有一组耗时的操作,这些操作专门针对我的应用的每个用户,它全部封装在一个方法中(例如write_collections方法)。在这个方法中,程序与Facebook和MongoDB进行通信。我想在每个用户的线程中运行此方法。

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

举例说明:


方法I

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

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

  • 足够安全吗?

详细

来自Object#object_id的官方Ruby文档:

  

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

ObjectSpace

  

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

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

让我们假设以下情况:

  1. 用户 A 访问'/' [现在 A 主题以object_id a 启动
  2. 线程 A 已完成[它已不再有效且已发布object_id]
  3. 用户 B 访问'/' [现在 B 主题使用相同的object_id a (* < em>有可能吗?)]
  4. 用户 访问'/calculate' [session[:data_thread] a ,因此ObjectSpace._id2ref(session[:data_thread])实际上是 B 主题]
  5. 状态不一致 - 用户 A 正在等待主题 B

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

  6. 方法II

    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
    

    详细

    我在阅读Sinatra: README后试过这个。 在配置

      

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

    范围和绑定下,应用程序/类范围

      

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

    我正在使用顶级DSL。

      

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

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

    摘要

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

    欢迎任何评论或参考。

1 个答案:

答案 0 :(得分:2)

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

根据问题中应用的说明,您希望在calculate方法完成后运行一些write_collections方法。那么,为什么write_collections方法不能调用某些calculate方法呢?或者,为什么不能使用后置滤波器或观察器进行计算?

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

  1. calculate功能
  2. GET /calculate
  3. 我认为应该只有一个触发器来调用calculate功能。在用户请求write_collections }时,它可以在GET /calculate 完成时完成。

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