写点什么

Ruby 1.9 加入纤程实现轻量级并发

  • 2007-09-05
  • 本文字数:3326 字

    阅读完需:约 11 分钟

Ruby 的线程成为人们的讨论议题已经很长时间了。未来版本的Ruby(1.9 及更高)是否将使用内核线程(kernel thread)替代用户线程(userspace threads),仍有待决定。最近,关于Ruby 中这一系列问题的另一条途径已经出现。David Flanagan 指出了 Ruby 1.9 分支中的一个新特性——纤程(Fiber)

下面是如何使用新的 Fiber 类(注意:类名可能会改变)生成(generate)一个 Fibonacci 数的无穷序列的范例。我使用“生成(generate)”这个词采用的是在 Python 中生成器(generator)的意义上的含义。Ruby 中新的纤程是半协程(semi- coroutines)。

1 fib = Fiber.new do<br></br>2 x, y = 0, 1<br></br>3 loop do<br></br>4 Fiber.yield y<br></br>5 x, y = y, x + y<br></br>6 end<br></br>7 end<br></br>8 20.times { puts fib.resume }这段代码会打印出前 20 个 Fibonacci 数。它使用了一个叫做协程(Coroutine)的概念。基本上,调用 Fiber.yield方法会停止(“挂起”)这段代码的执行(请不要把它和用于执行 block 的yield关键字相混淆)。如果你熟悉调试器,那么请想象一下对某个线程点击“挂起”按钮或者看这个线程因为断点命中而中断。fib是这个纤程的句柄,你可以用它对纤程进行操作。在第 8 行出现的fib.resume所完成的正是该代码所表达的意思:它恢复了Fiber.yield调用之后语句的执行,使该纤程恢复执行状态。

第 4 行显示了Fiber.yield带着一个参数y被调用。在某种程度上,这可以被认为是与return y相类似。子程序(subroutine)的return和协程的Fiber.yield之间的区别在于代码调用后其上下文发生的变化上。这就是说,return的意思是一个函数调用的激活格局(或者叫做栈格局)被取消分配了(deallocate),即所有的本地变量都消失了。在一个协程里,yield保存着这个激活格局并且其内部的所有数据还是存在的,因此在调用了resume方法之后,代码还能继续使用这些环境。

现在这段代码如何工作就变得很清晰了:它只是一个循环——通过相继迭代的方式计算出 Fibonacci 熟。一旦完成一次计算,它就将自己挂起,并将 CPU 的控制权让给别人。当代码性药序列中的下一个数时,它只需要恢复 Fibonacci 代码,代码运行下一次迭代,然后通过调用Fiber.yield将自己挂起让出控制权(以及下一个 Fibonacci 数)。

这是在 Ruby 1.9 分支中加入的一个相当新的特性,而看起来它的细节仍未确定。纤程这个术语对于 Windows 程序员来说,可能并不陌生。 MSDN 中是这么解释的

纤程是一个必须由应用程序手动调度的执行单位。纤程运行在调度线程的上下文中。每一个线程可以调度多个纤程。一般来说,纤程所能带来的优势是不及一个设计优良的多线程应用的。但是,对于被设计成必须调度自身线程的应用程序来说,使用纤程可以让它们的移植变得更为容易。

Ruby 1.9 VM(以前被称作YARV)的开发者笹田耕一(Sasada Koichi)就此在 ruby-core 邮件列表中给出了更详细的一些信息

这些方法名(resume/yield)来自 Lua。“transfer”来自 Modula-2,“double resume error”来自 Python 生成器。顺便说一句,我正在考虑使用“纤程(Fiber)”这个名字。目前“纤程”的意思等同于“半协程(Semi-Coroutine)”。Fiber::Core 就是协程。是的,“纤程”这个名字是来自于微软,但是它的意义等价于半协程,如 Lua 的协程和 Python 的生成器。

半协程不对称(asymmetric)协程,在它们对控制权传递的选择上收到限制。不对称协程只能将控制权传递它们的调用者,而对于协程来说,只要它们有相应的句柄,它们就可以自由地将控制传递给其它的任何一个纤程。

前面的例子展现了一个半协程是如何被当成一个生成器来用的。比如,便利地生成 Fibonacci 数。某些语言,比如 Python,在语言中支持生成器且有特定的语法支持。从引文中看来,似乎半协程(纤程)和协程(Fiber::Core)行为都是支持的。最终 Ruby 1.9 及以后的版本中会出现什么以及它会如何命名尚待分晓,但是 Ruby 语言的创造者松本行弘(Matsumoto, Yukihiro)认为他们安全:

在核心开发人员之间仍然有热烈的讨论,但是纤程(和外部迭代器)比起 Continuation,更可能留在最终的 1.9 中。

注:Continuation,一个长期在 Ruby 1.9 分支中缺席的特性。尽管对于它是否可以使用 Ruby 1.9 的核心线程实现,人们尚存疑虑,但在五月份它仍被加到Ruby 1.9 中

除了实现了控制结构,协程提供了一种使用轻量级并发(lightweight concurrency)的方式。实际上它们允许使用协同调度来实现用户线程。协程既能将控制权让给彼此,也能通过将控制权交给调度协程,让其决定下一个调度的协程这种方式来实现集中式调度。

这样,关于Ruby 1.9 转向更为重量级的内核线程的疑虑,就迎刃而解了。Ruby 1.8 线程被构建成一个用户线程系统,这样做的好处是线程管理的开销更少。创建一个内核线程要进行对操作系统的系统调用,比起对线程系统的进程内调用要费时得多。例如,JRuby 使用内核线程,但也在尝试使用线程池来弥补创建开销的性能不足。

然而,创建大量的内核线程仍旧有许多性能开销,或者会直接导致对线程有硬性数量限制或者对大量线程的支撑力不从心的操作系统出现问题。在这些情况下,一个轻量级的选择是非常有用的。如果解决方案在逻辑上可行而又简洁的话,它允许代码分配在不同的线程中执行,并且保持低水平的开销。这个方案的另一个优点是,如果需要调用一个长时间运行的操作或者进行一次系统调用,但又不能阻塞进程中所有代码的执行,那么仍然可以使用内核线程。

Erlang 采用了一个相似的方法,它也提供轻量级进程,但 Erlang 的进程不共享任何东西,与之相反,纤程共享同一个地址空间。然而,线程的存在使得人们可以采用参与式(Actor-style)的编程方式,而不需要担心额外性能开销。

在 Ruby 领域,纤程的概念也不是全新的。Rubinius 拥有 Tasks,其描述和 Ruby 1.9 的纤程类似。(InfoQ 最近有一篇新闻报导采访了 Rubinius 项目的领导人 Evan Phoenix 对 Rubinius 中这种线程模型的观点)。 MenTaLguY 就此在 ruby-core 上详细描述说

不过,在现代的并发环境中,它们 [纤程] 变得越来越有用。没有它们或者和它们相似的东西(比如 Rubinius 的 Tasks),你就得用一些丑陋的技巧才能实现轻量级并发了——你可以看看在 Scala 的 actors 类库中明确地使用 continuation-passing(指函数,而不是 Continuations),这就是它们还没有问世的时候,我们可以期望的最好的例子。 我同意,使用纤程会让 JRuby 的日子变得不是那么好过。

最后的评论提出了一个重要的观点。如果纤程被 Ruby 采用,那么对于 Ruby 在 JVM 和 CLR 上的实现,如 JRuby、XRuby、Ruby.NET 或者 IronRuby 来说,将出现一个头疼的问题。现在,它们当中还没有一个支持 Continuations,因为在这些虚拟机上操作或者读取调用栈(callstack)是非常困难,或者几乎不可能实现的。是否需要实现 Continuations 是一个很有争议的问题,然而它好像并没有给 JRuby 等带来什么大难题,因为它并没有广泛地应用在 Ruby 之中。在 Ruby 1.8 的标准库中,唯一的应用就是生成器的实现,但是,举例而言,在 JRuby 1.0 中没有使用 Continuations 也一样实现了此功能。

虽然可以避开这些问题来实现这些特性,但是问题在于这些迂回解决方法是否会引起性能下降。例如,如果调用栈必须在堆上仿真,而不是使用虚拟机的栈,这样做可能引起性能的下降,或者会阻止(JIT)编译器的的优化操作。对于非对成协程的迂回解决方法会更容易一些,因为它们使用虚拟机的栈进行方法调用。像 C#这类的语言以这种方式实现了它们的迭代器(Iterator)特性,这些迭代器允许人们用和上面的示例代码相似的方式编写生成器。

查看英文原文: Ruby 1.9 adds Fibers for lightweight concurrency - - - - - -

译者简介:仝键,网名“咖啡屋的鼠标”,普通程序员。喜欢思考,沉默时沉闷至死,说起来却又无边无际。爱好广泛常恐有贪多不精之后遗症。从小接触电脑却白白荒废十余年光阴,直至大学之后才入编程之门。如今漂泊北京寻找着自己的一片天地。感兴趣的技术领域有 Agile、Java、设计模式、Flex、Ruby 和面向对象数据库等。个人技术博客为 http://blog.csdn.net/tj19832/ 。参与 InfoQ 中文站内容建设,请邮件至 china-editorial[at]infoq.com

2007-09-05 01:303593

评论

发布
暂无评论
发现更多内容

论“内卷”

容光

20 岁发表 SCI 的学霸,梦想用算法改变世界

阿里云CloudImagine

学习 阿里云 算法 计算机视觉 音视频

Vue.js项目无法启动:sh: 1: vue-cli-service: not found

codists

vue.js

高性能JavaScriptの结语 -- 学完了

空城机

JavaScript 6月日更

【源码系列】Spring Boot 条件配置

Alex🐒

spring 源码 StringBoot

打开算法黑箱之谜 !“可解释机器学习”在金融领域的应用

索信达控股

大数据 AI 算法 金融科技 模型开发

小程序电商系统微服务拆分

唐高为

week6 作业

Geek_2e7dd7

架构实战营

数组的 sort() 方法详解

编程三昧

JavaScript 编程 算法 大前端 数组

网络基础:HTTP 服务介绍

看山

网络 HTTP 6月日更

PostgreSQL 的审计和权限控制系统

GrowingIO技术专栏

postgresql

【21-2】什么是 PowerShell

耳东@Erdong

windows PowerShell 6月日更

【Vue2.x 源码学习】第九篇 - 对象数据变化的观测情况

Brave

源码 vue2 6月日更

浅析 PHP 的「垃圾回收」机制

悟空聊架构

php 垃圾回收 6月日更 引用计数

MySQL基础之七:内连接详解

打工人!

myslq 6月日更

Bzz云算力挖矿APP开发,Bzz矿机分币系统

分布式认知工业互联网的更多“玩法”介绍

CECBC

再谈业务信息化操作系统(BIOS)

孤岛旭日

中台 企业架构 平台

中文技术文章排版

KooFE

6月日更

Packer 自动化镜像构建

HoneyMoose

理解rust函数中的生命周期

wzx

rust 生命周期

Kubernetes手记(6)- POD 配置清单

雪雷

k8s 6月日更

Go Runtime的调度器

面向加薪学习

开发 Go 语言

springboot启动流程之createApplicationContext

梦倚栏杆

【源码系列】Spring Boot 自动装配

Alex🐒

spring 源码 Spring Boot

公安警情大数据情报研判分析系统开发,可视化大屏开发

优化算法

容光

请问我要做全栈程序员么?听说更厉害更赚钱?

程序员鱼皮

Java c++ Python 大前端 全栈

低内聚:说是小需求做似趟雷区

Jxin

API 中签名的使用

架构精进之路

签名 6月日更

SkipGram

Qien Z.

nlp 6月日更 SkipGram negative sampling CBOW

Ruby 1.9加入纤程实现轻量级并发_Ruby_Werner Schuster_InfoQ精选文章