Ruby 的GIL 到底做了什么

2017/05/13 Ruby

GIL 是 Global Interpreter Lock 也叫 GVL

无人知晓的GIL中文版 这篇文章对GIL描述的比较清楚了。Ruby 或者说Rails中由于GIL是没有并行操作的,由于GIL的存在,每次操作只能使用一个CPU的核心。

This is bullshit。

看下面的例子, 是在CRuby中:

require 'benchmark'

@threads = []
Benchmark.bm(14) do |x|
  x.report('single-thread') do
    8.times do
      tmp_array = []
      10_000_000.times { |n| tmp_array << n }
    end
  end

  x.report('multi-thread') do
    8.times do
      @threads << Thread.new do
        tmp_array = []
        10_000_000.times { |n| tmp_array << n }
      end
    end
    @threads.each(&:join)
  end
end

     user     system      total        real
single-thread
  5.240000   0.580000   5.820000 (  5.880398)
multi-thread
  6.450000   0.710000   7.160000 (  7.234476)

multi-thread 比single-thread 还慢。这里是由于GIL的作用,导致了这样的结果。

require 'faraday'

@conn = Faraday.new(url: 'https://ruby-china.org')
@threads = []

Benchmark.bm(14) do |x|
  x.report('single-thread') do
    20.times { @conn.get }
  end

  x.report('multi-thread') do
    20.times do
      @threads << Thread.new { @conn.get }
    end
    @threads.each(&:join)
  end
end

#    user     system      total        real
# single-thread
#   0.190000   0.020000   0.210000 (  4.345562)
# multi-thread     0.090000   0.020000   0.110000 (  0.269009)

multi-thread 的消耗时间比 single-thread 少快20倍。可以看出,和上一个例子相比,多线程起到了作用。

为什么会这样呢?Ruby的GIL没有起到作用吗?

Ruby GIL doesn’t block IO operations. 所以,像http requests, 文件IO操作,是不会被GIL所限制的。

对于MySQL connection,使用mysql2 gem,也解决了GIL限制的问题,Ruby 程序也能并行的向MySQL Server  发送连接请求,或者并行使用连接池查寻。No GIL。

关于Ruby 的web服务器。 Puma 使用的是多线程的方案。 对于http 请求, Puma 是能真正使用多线程处理的,而不会被GIL所限制, 所以Puma拥有更好的并发处理能力(理论上来说)。 但是,多线程拥有永恒的问题,竞态条件、竞态资源问题。如果,你写了并非是线程安全的代码,也许就会导致奇特的bug出现了。比如:

@name ||= name

# source code
if @name == nil
  @name = name
end
name

这种先检查-再设置的操作,就有可能让@name 初始化两次。如果你有复杂的代码逻辑,也许就会出现奇怪的问题。

也许GIL限制了Ruby现在的发展,限制了Ruby的并发性能。期待Ruby3,能给Ruby带来新的革命。

 

Search

    Table of Contents