如何知道是不是线程安全的红宝石?(how to know what is NOT thread-sa

2019-07-20 20:52发布

从Rails的4起 ,一切都将在默认情况下在多线程环境中运行。 这意味着我们所编写的代码 所有我们使用的都是需要是宝石threadsafe

所以,我对这个几个问题:

  1. 究竟是不是线程安全的的Ruby / Rails? VS什么是线程安全的的Ruby / Rails?
  2. 是否有称为是线程安全的,反之亦然宝石的名单?
  3. 是有未线程示例的代码共同模式的列表@result ||= some_method
  4. 在红宝石郎核心的数据结构,如Hash等线程?
  5. MRI检查,那里有一个GVL / GIL这意味着只有1红宝石线程可以在除一个时间运行IO ,莫非是线程安全的变化影响我们呢?

Answer 1:

核心数据结构都不是线程安全的。 我知道,附带的Ruby的只有一个标准库队列执行( require 'thread'; q = Queue.new )。

MRI的GIL不会线程安全问题拯救我们。 它只可以确保两个线程不能同时在两个不同的CPU在精确的同时运行Ruby代码,即。 线程仍然可以暂停,并在你的代码中的任何点恢复。 如果你喜欢写代码@n = 0; 3.times { Thread.start { 100.times { @n += 1 } } } @n = 0; 3.times { Thread.start { 100.times { @n += 1 } } }如突变来自多个线程的共享变量,共享变量的值之后是不确定性的。 吉尔是或多或少的单核系统的模拟,它不会改变编写正确的并发程序的基本问题。

即使MRI已经单线程像Node.js的,你仍然要考虑并发性。 与增加的变量的例子会工作得很好,但你仍然可以得到比赛的条件,其中的事情发生在不确定的顺序和一个回调则会覆盖的另一个结果。 单线程异步系统更易于推理,但他们不是从并发问题免费。 试想有多个用户的应用程序:如果两个用户打在或多或少同一时间堆栈溢出后编辑,花一些时间,编辑帖子,然后点击保存,其变动由第三方用户以后可以看到,当他们读了同一个岗位?

在Ruby中,因为在大多数其他并发运行时,任何一个以上的操作是不是线程安全的。 @n += 1不是线程安全的,因为它是多个操作。 @n = 1是线程安全的,因为它是一个操作(它的很多引擎盖下操作的,我可能会惹上麻烦,如果我试图说明为什么它的“线程安全”的细节,但最终你不会得到从分配不一致的结果)。 @n ||= 1 ,是不和任何其他速记操作+分配是任一。 一个错误我犯了很多次是写作return unless @started; @started = true return unless @started; @started = true ,这不是线程安全的。

我不知道Ruby的线程安全和非线程安全的声明中的任何权威的排行榜,但有一个简单的经验法则:如果表达式只做一个(没有副作用)操作它可能是线程安全的。 例如: a + b是确定, a = b也行,和a.foo(b)是确定的, 如果该方法foo是无副作用 (因为只是在红宝石什么是一个方法调用,即使在分配很多情况下,这也适用于其他的例子也是如此)。 在这方面的副作用是指事物改变状态。 def foo(x); @x = x; end def foo(x); @x = x; end 没有副作用自由。

其中一件关于Ruby中编写线程安全的代码的最困难的事情是,所有的核心数据结构,包括数组,散列和字符串,是可变的。 这很容易不小心泄露了一块你的状态,而当那件是可变的东西可以得到真正搞砸了。 考虑下面的代码:

class Thing
  attr_reader :stuff

  def initialize(initial_stuff)
    @stuff = initial_stuff
    @state_lock = Mutex.new
  end

  def add(item)
    @state_lock.synchronize do
      @stuff << item
    end
  end
end

这个类的一个实例可以在线程之间共享,他们可以安全的东西给它添加,但有一个并发错误(这是不是唯一的一个):通过对象泄漏的内部状态stuff访问。 除了是从封装角度看问题,这也开启了并发一罐蠕虫。 也许有人需要这个数组并将其传递到别处,并依次代码认为它现在拥有该阵列,可以为所欲为它。

另一个典型的例子红宝石是这样的:

STANDARD_OPTIONS = {:color => 'red', :count => 10}

def find_stuff
  @some_service.load_things('stuff', STANDARD_OPTIONS)
end

find_stuff工作正常,它使用的第一次,但返回别的东西的第二次。 为什么? 该load_things方法恰好认为它拥有的选项哈希值传递给它,并执行color = options.delete(:color) 。 现在STANDARD_OPTIONS常数不具有相同的值了。 常量是唯一不变的,他们引用什么,他们并不能保证他们指的是数据结构的恒定性。 试想,如果这些代码同时运行会发生什么。

如果您避免共享可变的状态(例如,在由多个线程访问的对象实例变量,数据结构一样被多个线程访问哈希和数组)线程安全并不难。 尝试正在同时访问应用程序的部分最小化,并集中你的努力有。 IIRC,在Rails应用程序,为每个请求创建一个新的控制器对象,因此它只会习惯由单个线程,同样也适用于任何模型对象,你从控制器创建。 然而,导轨还鼓励使用全局变量( User.find(...)使用全局变量User ,你可能会认为它是唯一的一类,它是一类,但它也是全局变量的命名空间),其中一些是安全的,因为它们是只读的,但有时你保存的东西在这些全局变量,因为它很方便。 要非常小心,当你使用什么是全局访问。

它已经可以在线程环境现在运行Rails相当长的一段时间,所以没有被Rails的专家我还是会走这么远说,你不必担心线程安全的,当涉及到Rails本身。 您仍然可以创建不是线程做一些我上面提到的东西安全Rails应用程序。 当谈到其他宝石假定他们,除非他们说,他们是不是线程安全的,如果他们说,他们认为他们都没有,并通过他们的代码看起来(但只是因为你看到他们去之类的东西@n ||= 1并不意味着它们不是线程安全的,这是在正确的范围内做一个完全合法的事情-你应该不是找东西像可变状态中的全局变量,如何处理传递给它的方法可变对象,而且特别是如何处理的选项哈希)。

最后,是线程不安全是一个传递特性。 使用的东西,是不是线程安全本身不是线程安全的事情。



Answer 2:

除了西奥的回答,我会在Rails的几个问题的区域添加到了望具体而言,如果你切换到config.threadsafe!

  • 类变量

    @@i_exist_across_threads

  • ENV:

    ENV['DONT_CHANGE_ME']

  • 主题

    Thread.start



Answer 3:

从Rails的4起,一切都必须在默认线程环境中运行

这是不是100%正确。 线程安全的Rails只是默认。 如果部署像客运(社区)或独角兽多进程应用服务器上会有根本没有区别。 这一变化仅涉及您,如果部署在多线程环境,比如彪马或客运企业> 4.0

如果您想多线程应用程序服务器上部署的过去,你不得不打开config.threadsafe,这是默认了,因为它所作的有要么没有效果,或者也适用于一个单一的进程中运行(一个Rails应用程序Prooflink )。

但是,如果你希望所有的Rails 4 流的好处和多线程部署的其他实时琐事,那么也许你会觉得这文章有趣。 作为@Theo悲伤,对于一个Rails应用程序,你其实只需要省略请求过程中变异静止状态。 虽然这种简单的做法可循,不幸的是你不能确定这对每个你找到宝石。 至于我记得从JRuby项目查尔斯·奥利弗·纳特有关于它的一些提示这个播客。

如果你想要写一个纯粹的并发Ruby编程,在那里你会需要它们由多个线程访问的一些数据结构,你也许会发现thread_safe宝石有用。



文章来源: how to know what is NOT thread-safe in ruby?