Ruby 3 Guilds并发模型(译)

作者: falm | 来源:发表于2016-10-16 13:04 被阅读230次

    文本为翻译Olivier Lacan在其博客上发布的文章,并对内容上做了适当的调整。

    英文原文地址:http://olivierlacan.com/posts/concurrency-in-ruby-3-with-guilds/

    在Ruby Kaigi 2016 大会上,Ruby VM和GC的作者 Koichi Sasada 为Ruby 3 提出了一个新的并发模型。

    我们都知道Ruby 提供了线程去实现并发,但是MRI并不能让Ruby的代码并行的运行。Koichi 致力于解决Ruby并行中,遇到的各种挑战,包括可变对象,竞态条件和线程间同步,最终他提出了一个新的并行并发机制,称为 Guilds。

    并发的目标

    如果你对并发和并行不太了解,可以先阅读一下这篇文章:并发与并行的区别

    为Ruby3 提出 Guilds的基本前提是,保证与Ruby2兼容的情况下实现并行,考虑到GIL限制了并发,所以要尝试通过实现,快速对象共享和提供特殊的对象去共享可变对象。

    实现并发在目前的Ruby版本中是一件很困难的事情,这因为程序员需要手动的管理线程,确保不会出现竞态条件。通用的做法是在线程中引入『锁』,像Ruby就提供了线程互斥锁,但它或多或少违反了并行的初衷,『锁』容易使程序变慢,而且不当使用的话,很可能使得并发程序比相同的同步程序还要慢。

    Guilds工作原理

    Koichi的允许下,我为了让它更好理解,就对他的提案做了适当的缩减。

    Guild是基于现有的 线程(thread)和纤程(fiber)实现的。它至少由一个线程组成,并且该线程至少由一个纤程组成。不同Guild中的线程可以并行运行,而相同Guild中的线程不能。一个Guild不能对其他Guild中的对象进行读写。

    同一个Guild中的不同线程不能并发运行,这是因为Guild存在一个叫 GGL(Giant Guild Lock)的锁,它是用来确保线程是先后运行的,而不同Gulid的线程是可以并发运行的。

    你也可以认为 Ruby 2.x 的程序,就相等于是存在一个Guild的程序。

    Guilds间通信

    不同Guild间不可以相互读写可变对象,这保证了Guild并发运行时,不会因为并发读写而引发问题。

    不可相互读写

    不过,Guild可以通过Guild::Channel 提供的接口,将对象复制或移动到其他的Guild中。

    Guild::Channeltransfer(object)可以将一个对象深度拷贝到目标Guild中。

    同样也可以使用,Guild::Channel中的transfer_membership(object) 将一个对象完全移动到其他的Guild中。

    一旦对象被移动到新的Guild后,如果原来持有该对象的Guild,再对它进行访问,将会抛出异常。

    这里我们就知道了,Guild是不能在没有复制或移动的情况下共享可变对象的,还有一点非常重要,那就是不可变对象(immutable objects)在『深度冻结』(意思是被该对象引用的对象也是冻结不可变的)的情况下是可以直接在多个Guild间共享的。

    下面这个例子,展示可变和不可变对象的区别:

    # 像整数,这样的数值类型,默认就是不可变的,而哈希不是。
    mutable = [1, { "key" => "value" }, 3].freeze
    
    # 而如果 数组实例及其引用的,字符串和哈希的实例都进行冻结,
    # 就会得到一个"深度冻结" 的不可变对象。
    immutable = [
      "bar".freeze,
      { "key" => "value".freeze }.freeze
    ].freeze
    

    使用方法

    在Koichi Sasada的研究中,他给出了几个关于如何使用Guilds的例子,我在这里,将其中最小的,关于使用Guild并行计算『斐波那契』的例子进行了简化进行展示。

    def fibonacci(n)
      return n if n <= 1
      fibonacci( n - 1 ) + fibonacci( n - 2 )
    end
    
    guild_fibonacci = Guild.new(script: %q{
      channel = Guild.default_channel
    
      while(n, return_channel = channel.receive)
        return_channel.transfer( fibonacci(n) )
      end
    })
    
    channel = Guild::Channel.new 
    guild_fibonacci.transfer([3, channel])
    
    puts channel.receive
    

    Guild对比线程的优势

    在线程中,我们很难判别出,哪些可变对象已经被共享了。而Guilds禁止可变对象的共享,转而提供简单的方式去共享不可变对象,而且Koichi计划提供,通过使用『特殊数据结构』来共享可变对象,『特殊数据结构』会自动隔离有风险的可变代码。

    当然,使用Guilds相较于线程来说,有些繁琐,这也是需要有取舍的。

    性能

    我的理解是,目前Guilds的"C"实现仅有400行代码,虽然该实现现阶段还不能用,但Koichi展示了,运行多个Guilds相比于运行单个Guilds在斐波那契例子上的性能优势。

    在双核的Linux虚拟机上运行Window 7,Koichi 观察到以下结果:

    这也许不能代表,现实场景中大部分Ruby应用会得到性能上的提升,但我还是等不及想知道,Guilds在RubyBench上的测试结果。

    总结

    Guilds是一种面向Ruby的,简单易用并且安全的并发方式,非常希望Koichi Sasada和Ruby核心团队能在之后,分享更多关于Guilds的资料。

    我对Guilds中移动和复制对象的方法的命名,有一些小看法,因为Guild::Channel. transfer(object)看上去更像是表示交换的意思,而结果仅仅是对象的深度拷贝,我相信transfer_copy(object)或是更简单的 copy(object) 更加适合。还有transfer_membership(object) 移动对象的方法,可以简化命名为channel.transfer(object) 。当然了这些方法的命名也不会是一成不变的。

    我非常期望,Ruby核心团队能够将这个新功能,作为实验性质的可选功能发布出去,这样Ruby社区就能参与进去帮助改进和测试。还可以让我们在2020年的Ruby3版本之前,就使用上这个新特性。Guilds将会对Ruby在并发友好性上,做出了积极的影响。

    相关文章

      网友评论

      本文标题:Ruby 3 Guilds并发模型(译)

      本文链接:https://www.haomeiwen.com/subject/ywioyttx.html