5

由于 Thin/Unicorn 是单线程的,您如何处理 Thread.current/per-request 存储?

只是运行了一个简单的测试——在一个会话中设置一个键,从另一个会话中读取它——看起来它一直在同一个地方写入/读取。虽然不会在 WEBrick 上发生。

class TestController < ApplicationController
  def get
    render text: Thread.current[:xxx].inspect
  end

  def set
    Thread.current[:xxx] = 1

    render text: "SET to #{Thread.current[:xxx]}"
  end
end

尝试添加config.threadsafe!到 application.rb,没有变化。

存储每个请求数据的正确方法是什么?

为什么会有使用 Thread.current 进行存储的 gem(包括 Rails 本身和倾斜)?他们如何克服这个问题?

难道 Thread.current 对每个请求都是安全的,但在请求后并不清楚,我需要自己做吗?

  • 用 Rails 3.2.9 测试

更新

总结下面与@skalee 和@JesseWolgamott 的讨论以及我的发现——

Thread.current 取决于运行应用程序的服务器。尽管服务器可能会确保没有两个请求在同一个 Thread.current 上同时运行,但此哈希中的值可能不会在请求之间被清除,因此在使用情况下 - 必须将初始值设置为覆盖最后一个值。

有一些著名的 gem 使用 Thread.current,比如 Rails、tilt 和 draper。我如果它被禁止或不安全,他们就不会使用它。似乎他们都在使用哈希上的任何键之前设置了一个值(甚至在请求结束后将其设置回原始值)。

但总的来说,Thread.current 并不是按请求存储的最佳实践。在大多数情况下,更好的设计会起作用,但在某些情况下,使用env会有所帮助。它可以在控制器中使用,也可以在中间件中使用,并且可以注入到应用程序的任何位置。

更新 2 - 就目前而言,draper 似乎错误地使用了 Thread.current。见https://github.com/drapergem/draper/issues/390

更新 3 - 该 draper 错误已修复。

4

3 回答 3

0

虽然人们仍然警告不要使用Thread.current存储“线程全局”数据,但在 Rails 中可能正确的方法是Thread.current使用 Rack 中间件清除对象。Steve Labnik 编写了request_store gem 来轻松做到这一点。gem 的源代码非常非常小,我建议您阅读它。

有趣的部分转载如下。

module RequestStore
  def self.store
    Thread.current[:request_store] ||= {}
  end

  def self.clear!
    Thread.current[:request_store] = {}
  end
end


module RequestStore
  class Middleware
    def initialize(app)
      @app = app
    end

    def call(env)
      RequestStore.clear!
      @app.call(env)
    end
  end
end

请注意,清理整个 Thread.current不是一个好习惯。request_store 基本上在做什么,是跟踪您的应用程序存储到 Thread.current 中的密钥,并在请求完成后将其清除。

于 2013-01-28T12:10:05.017 回答
0

您通常希望在会话中存储内容。如果你想要一些非常短暂的东西,请参阅Rails 的 flash。每次请求都会清除它。任何依赖线程的方法都不会在不同的网络服务器上始终如一地工作。

另一种选择是修改env哈希:

env['some_number'] = 5

BTW Unicorn 不仅仅是单线程的,它是分叉的。每次请求都会产生新进程(尽管听起来很吓人,但在 Linux 上非常有效)。因此,如果您在 Unicorn 中设置任何内容,即使是全局变量,它也不会持续到另一个请求。

于 2012-12-11T22:10:58.387 回答
0

使用 , 的警告之一Thread.current是,对于重用线程或具有线程池的服务器,在每次请求后进行清理变得非常重要。

这正是request_storegem 所提供的,一个类似的简单 API Thread.current,负责在每次请求后清理存储数据。

RequestStore[:items] = []

但请注意,gem 用于Thread.current保存 Store,因此它无法在每个请求有多个线程的多线程环境中正常工作。

为了规避这个问题,我实现了一个可以在同一请求的线程之间共享的存储。它被称为request_store_rails,用法非常相似:

RequestLocals[:items] = []
于 2015-04-11T16:52:24.790 回答