Ruby 聊聊 MRI Ruby Concurrent [未完成]

killyfreedom · 2014年07月07日 · 最后由 douxiance 回复于 2015年08月20日 · 8700 次阅读
本帖已被管理员设置为精华贴

随着项目越做越大,对 MRI Ruby 的几种并发服务模型也有了一部分的了解,也抛出来和大家聊聊,希望有点新的收获。

众所周知,MRI Ruby 是一个拥有 GIL 的 Ruby 实现,先天决定了在同一时刻,只会有一个线程被运行,虽然依然无法在根本上解决线程安全的问题,但是根本上来说,一个基础架构的偏向,会导致整体语言相关的社区的导向,简而言之,在 ruby 的世界里,绝大部分时间里,Thread,这个词,就是被忽略的命,纵使 rails,也是才开始重视多线程的问题。

然而,开发越来越多,做的越来越多,慢慢发现,GIL 真的是一个好大好大的限制,但,即使在这样的限制条件下,我们依然在尝试着各种不同的并发模型。

所有的并发模型,在 MRI 的 ruby 这个有 GIL 的实现下,最大的目的就是:

分离 IO 操作和 CPU 操作,让 IO 操作在执行的同时,CPU 并不会堵塞在 IO 等待中,从而实现更高的程序效率。

由 function1 --- io1 --- function2 --- io2 --- 这样的执行策略变成: function1 -- function2 -- funtion1 --- function 2 io1 --------io2---------io1-----------io2----

用一个比较粗略的方式来比较下这几种并发服务模型的效率. 假设,我们认为每个 function 就是一个独立的事务,那么,在事务和事务之间切换的性能也代表了其不考虑 IO 干扰的最高的并行能力,也就是说,只考虑这几个模型下的 Context Switch 的能力。

做了这几年,用到的最多的,就这几种了:

  1. EventMachine
  2. MultiThread
  3. Celluloid

EventMachine 是最早接触的了,本质上来说,EM 是一个巨大的 for 循环,将 IO 操作独立具体事务之外,将控制权让渡于事件核心,由事件核心以事件的方式触发流程继续。

写了一个粗略的测试其切换能力的代码:

require 'eventmachine'
$c = 0
Thread.start do
  EM.run do
    p = proc {
      $c += 1
      EM.next_tick(p)
    }

    EM.next_tick(p)
  end
end

arr = []

20.times do
  puts $c
  arr << $c unless $c == 0
  $c = 0
  sleep 1
end

puts "avg: " + (arr.inject(0) { |r, i| r + i } / arr.length).to_s

得到的平均值: avg: 96511

EventMachin 的缺点: callback 太多...

我们想要的代码是:

do_a
do_b
do_c
do_d

而不是

do_a {
    do_b {
        do_c {
           do_d
        }
    }
}

然后就是 MultiThread: 很神奇,在 1.9 之前,ruby 甚至是不支持 Native 线程的,其线程创建在其 VM 之上,直到 1.9 之后,线程才真正使用了系统级别的线程实现,线程之间的调度,由用户级变成了内核级,从轻量级的实现变成了重量级的实现,切换速度下降了,倒是也带来了不少更接近与原生实践的能力。

#encoding: utf-8
require 'thread'
require 'irb'
$c = 0

2.times do
  Thread.new do
    while true do
        # $cv.wait#($m)
      $c += 1
        # $cv.signal
      Thread.pass
    end
  end
end
arr = []

20.times do
  puts $c
  arr << $c unless $c == 0
  $c = 0
  sleep 1
end

puts "avg: " + (arr.inject(0) { |r, i| r + i } / arr.length).to_s

结果:

# 1 thread, avg: 755507
# 2 thread, avg: 114600
# 3 thread, avg: 56838
# 4 thread, avg: 37229
# 5 thread, avg: 35603
# 100 thread, avg: 31825

困了...Celluloid 等过两天再写....

先给点个赞

2 楼 已删除

jRuby 试过么?

EventMachine 也可以不写 callback 试试 em-synchrony

用多线程的话,可以考虑 agent,用的是 go 的写法,代码上会自然很多

https://github.com/igrigorik/agent EM 作者出品

Mark, 关注。

最后一句可以写成: puts "avg: " + (arr.inject(:+)/ arr.length).to_s

点个赞,关注一把

关注一下

最近买了一本松本行宏的《代码的未来》,书中有讲到 JRuby 是支持多核的,而 MRI 也有支持多核的非线程安全的版本。如果想要通过 MRI 来实现多核编程,且在不存在内存共享的时候,可以选择非线程安全。

另外,EventMatchine 是一个年代很久的 gem。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号