gpt4 book ai didi

ruby - 如何知道 ruby​​ 中什么不是线程安全的?

转载 作者:数据小太阳 更新时间:2023-10-29 06:18:32 24 4
gpt4 key购买 nike

starting from Rails 4 ,默认情况下,一切都必须在线程环境中运行。这意味着我们编写的所有代码 所有 我们使用的 gem 必须是threadsafe
所以,我对此有几个问题:

  • 什么在 ruby​​/rails 中不是线程安全的? VS 什么是 ruby​​/rails 中的线程安全?
  • 是否有已知线程安全的 gem 列表,反之亦然?
  • 是否有非线程安全示例的常见代码模式列表@result ||= some_method ?
  • ruby lang核心中的数据结构如Hash等线程安全?
  • 在 MRI 上,哪里有 GVL / GIL 这意味着一次只能运行 1 个 ruby​​ 线程,除了 IO ,线程安全的变化对我们有影响吗?
  • 最佳答案

    没有一个核心数据结构是线程安全的。我所知道的唯一一个 Ruby 附带的是标准库中的队列实现 (require 'thread'; q = Queue.new)。

    MRI 的 GIL 并没有使我们免于线程安全问题。它只能确保两个线程不能同时运行 Ruby 代码,即同时在两个不同的 CPU 上运行。线程仍然可以在代码中的任何一点暂停和恢复。如果您编写像 @n = 0; 3.times { Thread.start { 100.times { @n += 1 } } } 这样的代码,例如从多个线程改变一个共享变量,之后共享变量的值是不确定的。 GIL 或多或少是对单核系统的模拟,它不会改变编写正确并发程序的基本问题。

    即使 MRI 像 Node.js 一样是单线程的,您仍然必须考虑并发性。带有递增变量的示例可以正常工作,但您仍然可以获得竞争条件,其中事情以不确定的顺序发生并且一个回调破坏了另一个回调的结果。单线程异步系统更容易推理,但它们并非没有并发问题。试想一个有多个用户的应用程序:如果两个用户或多或少同时在 Stack Overflow 帖子上点击编辑,花一些时间编辑帖子然后点击保存,第三个用户稍后会看到他们的更改读同一个帖子?

    在 Ruby 中,与大多数其他并发运行时一样,任何不止一个操作的操作都不是线程安全的。 @n += 1 不是线程安全的,因为它是多个操作。 @n = 1 是线程安全的,因为它是一种操作(它有很多幕后操作,如果我试图详细描述它为什么是“线程安全的”,我可能会遇到麻烦,但最终你不会得到不一致的结果作业)。 @n ||= 1 不是,也没有其他速记操作+赋值。我犯过很多次的错误是写 return unless @started; @started = true ,这根本不是线程安全的。

    我不知道任何关于 Ruby 的线程安全和非线程安全语句的权威列表,但有一个简单的经验法则:如果表达式只执行一个(无副作用)操作,则它可能是线程安全的。例如:a + b 可以,a = b 也可以,a.foo(b) 也可以,如果方法 foo 没有副作用(因为 Ruby 中几乎任何东西都是方法调用,甚至在许多其他例子中赋值,这适用于也是)。在这种情况下,副作用意味着改变状态的事物。 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 访问器泄漏。除了从封装的角度来看是有问题的,它还打开了一 jar 并发蠕虫。也许有人拿走了那个数组并将它传递给其他地方,然后该代码又认为它现在拥有该数组并且可以对它做任何想做的事情。

    另一个经典的 Ruby 示例是这样的:
    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 应用程序中,为每个请求创建一个新的 Controller 对象,因此它只会被单个线程使用,您从该 Controller 创建的任何模型对象也是如此。但是,Rails 也鼓励使用全局变量( User.find(...) 使用全局变量 User ,你可能认为它只是一个类,它是一个类,但它也是全局变量的命名空间),其中一些是安全的因为它们是只读的,但有时您将内容保存在这些全局变量中,因为它很方便。使用可全局访问的任何内容时要非常小心。

    在线程环境中运行 Rails 已经有一段时间了,所以如果不是 Rails 专家,我仍然会说,当涉及到 Rails 本身时,您不必担心线程安全。您仍然可以通过执行我上面提到的一些操作来创建非线程安全的 Rails 应用程序。当涉及到其他 gems 时,假设它们不是线程安全的,除非他们说它们是线程安全的,如果他们说它们不是线程安全的,然后查看它们的代码(但只是因为你看到它们像 @n ||= 1 这样的东西)并不意味着它们不是线程安全的,这是在正确的上下文中完全合法的事情——你应该在全局变量中寻找诸如可变状态之类的东西,它如何处理传递给其方法的可变对象,尤其是它如何处理选项哈希)。

    最后,线程不安全是一个传递属性。任何使用非线程安全的东西本身都不是线程安全的。

    关于ruby - 如何知道 ruby​​ 中什么不是线程安全的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15184338/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com