Ruby 多线程的潜力和弱点分析

Web 应用大多是 IO 密集型的,利用 Ruby 多进程+多线程模型将能大幅提升系统吞吐量。其原因在于:当Ruby 某个线程处于 IO Block 状态时,其它的线程还可以继续执行。但由于存在 Ruby GIL (Global Interpreter Lock),MRI Ruby 并不能真正利用多线程进行并行计算。JRuby 去除了 GIL,是真正意义的多线程,既能应付 IO Block,也能充分利用多核 CPU 加快整体运算速度。

上面说得比较抽象,下面就用例子一一加以说明。

Ruby 多线程和 IO Block

先看下面一段代码(演示目的,没有实际用途):

代码如下:

# File: block_io1.rb

def func1
  puts "sleep 3 seconds in func1\n"
  sleep(3)
end

def func2
  puts "sleep 2 seconds in func2\n"
  sleep(2)
end

def func3
  puts "sleep 5 seconds in func3\n"
  sleep(5)
end

func1
func2
func3

代码很简单,3 个方法,用 sleep 模拟耗时的 IO 操作。 运行代码(环境 MRI Ruby 1.9.3) 结果是:

代码如下:

$ time ruby block_io1.rb
sleep 3 seconds in func1
sleep 2 seconds in func2
sleep 5 seconds in func3

real  0m11.681s
user  0m3.086s
sys 0m0.152s

比较慢,时间都耗在 sleep 上了,总共花了 10 多秒。

采用多线程的方式,改写如下:

代码如下:

# File: block_io2.rb

def func1
  puts "sleep 3 seconds in func1\n"
  sleep(3)
end

def func2
  puts "sleep 2 seconds in func2\n"
  sleep(2)
end

def func3
  puts "sleep 5 seconds in func3\n"
  sleep(5)
end

threads = []
threads << Thread.new { func1 }
threads << Thread.new { func2 }
threads << Thread.new { func3 }

threads.each { |t| t.join }

运行的结果是:

代码如下:

$ time ruby block_io2.rb
sleep 3 seconds in func1
sleep 2 seconds in func2
sleep 5 seconds in func3

real  0m6.543s
user  0m3.169s
sys 0m0.147s

总共花了 6 秒多,明显快了许多,只比最长的 sleep 5 秒多了一点。

上面的例子说明,Ruby 的多线程能够应付 IO Block,当某个线程处于 IO Block 状态时,其它的线程还可以继续执行,从而使整体处理时间大幅缩短。

Ruby GIL 的影响

还是先看一段代码(演示目的):

代码如下:

# File: gil1.rb

require 'securerandom'
require 'zlib'

data = SecureRandom.hex(4096000)

16.times { Zlib::Deflate.deflate(data) }

代码先随机生成一些数据,然后对其进行压缩,压缩是非常耗 CPU 的,在我机器(双核 CPU, MRI Ruby 1.9.3)运行结果如下:

代码如下:

$ time ruby gil1.rb

real  0m8.572s
user  0m8.359s
sys 0m0.102s

更改为多线程版本,代码如下:

代码如下:

# File: gil2.rb

require 'securerandom'
require 'zlib'

data = SecureRandom.hex(4096000)

threads = []
16.times do
  threads << Thread.new { Zlib::Deflate.deflate(data) }
end

threads.each {|t| t.join}

多线程的版本运行结果如下:

代码如下:

$ time ruby gil2.rb

real  0m8.616s
user  0m8.377s
sys 0m0.211s

从结果可以看出,由于 MRI Ruby GIL 的存在,Ruby 多线程并不能重复利用多核 CPU,使用多线程后整体所花时间并不缩短,反而由于线程切换的影响,所花时间还略有增加。

JRuby 去除了 GIL

使用 JRuby (我的机器上是 JRuby 1.7.0)运行 gil1.rb 和 gil2.rb,得到很不一样的结果。

代码如下:

$ time jruby gil1.rb

real  0m12.225s
user  0m14.060s
sys 0m0.615s

代码如下:

$ time jruby gil2.rb

real  0m7.584s
user  0m22.822s
sys 0m0.819s

可以看到,JRuby 使用多线程时,整体运行时间有明显缩短(7.58 比 12.22),这是由于 JRuby 去除了 GIL,可以真正并行的执行多线程,充分利用了多核 CPU。

总结:Ruby 多线程可以在某个线程 IO Block 时,依然能够执行其它线程,从而降低 IO Block 对整体的影响,但由于 MRI Ruby GIL 的存在,MRI Ruby 并不是真正的并行执行,JRuby 去除了 GIL,可以做到真正的多线程并行执行。

(0)

相关推荐

  • Ruby 多线程的潜力和弱点分析

    Web 应用大多是 IO 密集型的,利用 Ruby 多进程+多线程模型将能大幅提升系统吞吐量.其原因在于:当Ruby 某个线程处于 IO Block 状态时,其它的线程还可以继续执行.但由于存在 Ruby GIL (Global Interpreter Lock),MRI Ruby 并不能真正利用多线程进行并行计算.JRuby 去除了 GIL,是真正意义的多线程,既能应付 IO Block,也能充分利用多核 CPU 加快整体运算速度. 上面说得比较抽象,下面就用例子一一加以说明. Ruby 多线

  • Ruby多线程库(Thread)使用方法详解

    Thread是Ruby的线程库,Thread库已经内置在Ruby中,但如果想要使用线程安全的Queue.Mutex以及条件变量等,则需要手动require 'thread'. 主线程main 默认情况下,每个Ruby进程都具备一个主线程main,如果没有创建新的线程,所有的代码都将在这个主线程分支中执行. 使用Thread.main()类方法可获取当前线程组的主线程,使用Thread.current()可以获取当前正在执行的线程分支.使用Thread.list()可获取当前进程组中所有存活的线程

  • JAVA多线程与并发学习总结分析

    1.计算机系统使用高速缓存来作为内存与处理器之间的缓冲,将运算需要用到的数据复制到缓存中,让计算能快速进行:当运算结束后再从缓存同步回内存之中,这样处理器就无需等待缓慢的内存读写了. 缓存一致性:多处理器系统中,因为共享同一主内存,当多个处理器的运算任务都设计到同一块内存区域时,将可能导致各自的缓存数据不一致的情况,则同步回主内存时需要遵循一些协议. 乱序执行优化:为了使得处理器内部的运算单位能尽量被充分利用. 2.JAVA内存模型目标是定义程序中各个变量的访问规则.(包括实例字段.静态字段和构

  • Ruby多线程编程初步入门

    传统程序有一个单独的线程执行,包含该程序的语句或指令顺序执行直到程序终止. 一个多线程的程序有多个线程的执行.在每个线程是按顺序执行的,但是在多核CPU机器上线程可能并行地执行.例如,通常情况下在单一CPU的机器,多个线程实际上不是并行执行的,而是模拟并行交叉的线程的执行. Ruby的可以使用 Thread 类很容易地编写多线程程序. Ruby线程是一个轻量级的和高效的在代码中实现并行性. 创建Ruby线程: 要启动一个新线程,关联一个块通过调用Thread.new.将创建一个新的线程执行的代码

  • Python基于多线程操作数据库相关问题分析

    本文实例分析了Python多线程操作数据库相关问题.分享给大家供大家参考,具体如下: python多线程并发操作数据库,会存在链接数据库超时.数据库连接丢失.数据库操作超时等问题. 解决方法:使用数据库连接池,并且每次操作都从数据库连接池获取数据库操作句柄,操作完关闭连接返回数据库连接池. *连接数据库需要设置charset = 'utf8', use_unicode = True,不然会报中文乱码问题 *网上说解决python多线程并发操作数据库问题,连接时使用self.conn.ping(T

  • Python多线程threading模块用法实例分析

    本文实例讲述了Python多线程threading模块用法.分享给大家供大家参考,具体如下: 多线程 - threading python的thread模块是比较底层的模块,python的threading模块对thread做了一些包装,可以更加方便的被使用. 1. 使用threading模块 单线程执行 #coding=utf-8 import time def saySorry(): print('跑一圈') time.sleep(1) if __name__ == "__main__&qu

  • Python实现多线程的两种方式分析

    本文实例讲述了Python实现多线程的两种方式.分享给大家供大家参考,具体如下: 目前python 提供了几种多线程实现方式 thread,threading,multithreading ,其中thread模块比较底层,而threading模块是对thread做了一些包装,可以更加方便的被使用. 2.7版本之前python对线程的支持还不够完善,不能利用多核CPU,但是2.7版本的python中已经考虑改进这点,出现了multithreading  模块.threading模块里面主要是对一些

  • .Net多线程编程(误用点分析)

    1 共享变量问题 错误写法: 所有的任务可能会共享同一个变量,所以输出结果可能会一样. public static void Error() { for(int i=0;i<10;i++) { Task.Run(() => { Console.WriteLine("{0}", i); }); } } 正确写法: 将变量i赋给局部变量temp,使得每一个任务使用不同的i值. public static void Right() { for (int i = 0; i <

  • ruby中并发并行与全局锁详解

    前言 本文主要给大家介绍了关于ruby并发并行和全局锁的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 并发和并行 在开发时,我们经常会接触到两个概念: 并发和并行,几乎所有谈到并发和并行的文章都会提到一点: 并发并不等于并行.那么如何理解这句话呢? 并发: 厨师同时接收到了2个客人点了的菜单需要处理. 顺序执行: 如果只有一个厨师,那么他只能一个菜单接着一个菜单的去完成. 并行执行: 如果有两个厨师,那么就可以并行,两个人一起做菜. 将这个例子扩展到我们的web开发

  • Python之多线程爬虫抓取网页图片的示例代码

    目标 嗯,我们知道搜索或浏览网站时会有很多精美.漂亮的图片. 我们下载的时候,得鼠标一个个下载,而且还翻页. 那么,有没有一种方法,可以使用非人工方式自动识别并下载图片.美美哒. 那么请使用python语言,构建一个抓取和下载网页图片的爬虫. 当然为了提高效率,我们同时采用多线程并行方式. 思路分析 Python有很多的第三方库,可以帮助我们实现各种各样的功能.问题在于,我们弄清楚我们需要什么: 1)http请求库,根据网站地址可以获取网页源代码.甚至可以下载图片写入磁盘. 2)解析网页源代码,

随机推荐