[翻译] Ruby Fiber Scheduler - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Mark24
V2EX    Ruby

[翻译] Ruby Fiber Scheduler

  •  
  •   Mark24 2023-10-12 15:46:08 +08:00 2440 次点击
    这是一个创建于 795 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Fiber Scheduler(纤程调度器)在 Ruby 中实现异步编程。该功能是 Ruby 3.0 的一大增强功能,并且也是优秀的 async gem 的核心组件之一。 最棒的一点是,你并不需要一个完整的框架就能开始!只需使用一对内置的 Ruby 方法,就能独立地实现纤程调度器并享受到异步编程的好处。

    纤程调度器主要包括两部分:

    • Fiber Scheduler interface (纤程调度器接口) 这是一套内置于编程语言中的阻塞操作钩子。钩子实现被委托给 Fiber.scheduler 对象。

    • Fiber Scheduler implementation (纤程调度器的实现) 实现了异步行为。这是一个需要程序员显式设置的对象,因为 Ruby 不提供默认的 Fiber Scheduler (纤程调度器)实现。

    非常感谢 Samuel Williams !他是 Ruby 的核心开发者,设计并实现了纤程调度器这一功能并整合到了语言中。

    Fiber Scheduler interface (纤程调度器接口)

    Fiber Scheduler (纤程调度器)接口是一套阻塞操作的钩子,它允许在阻塞操作发生时插入异步行为。它像是带有反转的回调:当异步回调被执行时,主阻塞方法不会运行。 这些钩子在 Fiber::SchedulerInterface 类中有文档记录。这个 Ruby 功能背后的一些主要思想包括:

    • 钩子是低层级的。这导致了少量的钩子,每个钩子处理许多高层级方法的行为。例如,#address_resolve 钩子负责处理大约 20 个方法。
    • 钩子只在 Fiber.scheduler 对象设置后才会工作,钩子的实现被委托给该对象。
    • 钩子的行为应该是异步的。

    Hook implementation (钩子实现)

    让我们看一个示例,显示如何实现 Kernel#sleep 钩子。在实践中,所有的钩子都是用 C 语言编写的,但为了清晰起见,这里使用了 Ruby 伪代码。

    module Kernel def sleep(duration = nil) if Fiber.scheduler Fiber.scheduler.kernel_sleep(duration) else synchronous_sleep(duration) end end end 

    以上代码的阅读方式如下:

    • 如果设置了 Fiber.scheduler 对象 - 运行其 #kernel_sleep 方法。#kernel_sleep 应该异步运行 sleep
    • 否则,执行常规的 synchronous_sleep,它会阻塞当前线程直到 sleep 完成。

    其他的钩子的工作方式也类似。

    Blocking operations (阻塞操作)

    已经多次提到了"Blocking operations (阻塞操作)"这个概念,但它到底是什么意思呢?阻塞操作是指任何 Ruby 进程(更具体地说:当前线程)最终会等待的操作。一个更具描述性的名称是“waiting operations (等待操作)”。 一些例子如下:

    作为一个反例,以下代码片段需要一段时间才能完成,但不包含阻塞操作:

    def fibonacci(n) return n if [0, 1].include? n fibonacci(n - 1) + fibonacci(n - 2) end fibonacci(100) 

    获取 fibonacci(100) 的结果需要等待很长时间,但只有程序员在等待!整个时间 Ruby 解释器都在工作,后台进行计算。一个简单的斐波那契实现并不包含阻塞操作。

    发展对阻塞操作是什么(和不是什么)的直觉是值得的,因为异步编程的整个目标就是同时等待多个阻塞操作

    Fiber Scheduler implementation (纤程调度器实现)

    纤程调度器实现是 Fiber Scheduler 功能的第二大部分。

    如果你想在 Ruby 中启用异步行为,你需要为当前线程设置一个 Fiber Scheduler 对象。这是通过 Fiber.set_scheduler(scheduler) 方法完成的。实现通常是一个定义了所有 Fiber::SchedulerInterface 方法的类。

    Ruby 不提供默认的 Fiber Scheduler 类,也没有可以用于此目的的对象。这看起来不寻常,但实际上不将 Fiber Scheduler 实现包含在语言中是一个好的长期决定。最好将这种相对快速演变的关注点留在 Ruby 核心之外。 从头开始编写 Fiber Scheduler 类是一项复杂的任务,所以最好使用现有的解决方案。实现的列表,它们的主要区别和推荐可以在 Fiber Scheduler List 项目中找到。

    举个例子

    让我们来看看仅使用 Fiber Scheduler 可以做什么。 所有示例都使用 Ruby 3.1 和来自 fiber_scheduler gem 的 FiberScheduler 类,这个 gem 由我维护。这个 gem 对于示例来说不是一个硬性依赖项,因为如果将以下代码片段中的 FiberScheduler 替换为另一个 Fiber Scheduler 类,每个代码片段仍然应该可以工作。

    基本示例

    这里有一个简单的示例:

    require "fiber_scheduler" require "open-uri" Fiber.set_scheduler(FiberScheduler.new) Fiber.schedule do URI.open("https://httpbin.org/delay/2") end Fiber.schedule do URI.open("https://httpbin.org/delay/2") end 

    上面的代码创建了两个纤程,每个纤程都进行一次 HTTP 请求。这些请求并行运行,整个程序在 2 秒内完成。

    • Fiber.set_scheduler(FiberScheduler.new) 在当前线程中设置一个 Fiber Scheduler,这使得 Fiber.schedule 方法可以工作,且 fiber 可以异步行为。

    • Fiber.schedule { ... } 这是一个内置的 Ruby 方法,用于启动新的异步 fiber 。

    这个示例仅使用了标准的 Ruby 方法 - Fiber.set_schedulerFiber.schedule 自 Ruby 3.0 版本以来就一直可用。

    高级例子

    我们来看看运行多种不同操作是什么样子的:

    require "fiber_scheduler" require "httparty" require "open-uri" require "redis" require "sequel" DB = Sequel.postgres Sequel.extension(:fiber_concurrency) Fiber.set_scheduler(FiberScheduler.new) Fiber.schedule do URI.open("https://httpbin.org/delay/2") end Fiber.schedule do # Use any HTTP library HTTParty.get("https://httpbin.org/delay/2") end Fiber.schedule do # Works with any TCP protocol library Redis.new.blpop("abc123", 2) end Fiber.schedule do # Make database queries DB.run("SELECT pg_sleep(2)") end Fiber.schedule do sleep 2 end Fiber.schedule do # Run system commands `sleep 2` end 

    如果我们顺序运行这个程序,它大约需要 12 秒才能完成。但是由于这些操作是并行运行的,所以总的运行时间仅仅超过 2 秒。 你并不仅限于发起 HTTP 请求。任何内置在 Ruby 中或由外部 gem 实现的阻塞操作都可以工作!

    扩展示例

    这是一个简单的,显然是人为刻意的示例,同时运行一万个操作。

    require "fiber_scheduler" Fiber.set_scheduler(FiberScheduler.new) 10_000.times do Fiber.schedule do sleep 2 end end 

    上述代码的完成时间略超过 2 秒。

    由于其低开销,sleep 方法被选择用于扩展示例。如果我们使用网络请求,由于需要建立数千个连接并进行 SSL 握手等,执行时间将会更长。

    异步编程的主要优势之一是能够同时等待许多阻塞操作。阻塞操作数量的增加将增加这种优势。幸运的是,运行大量协程(fibers)非常简单。

    结论

    Ruby 只需要一个纤程调度器( Fiber Scheduler )和一些内置方法就可以异步工作 - 不需要任何框架!

    使其工作很容易。选择一个纤程调度器( Fiber Scheduler )实现,然后使用以下这些方法:

    • Fiber.set_scheduler(scheduler)为当前线程设置一个纤程调度器( Fiber Scheduler ),使阻塞操作能够异步执行。
    • Fiber.schedule { ... } 启动一个新的纤程,该纤程与其他纤程并发运行。

    一旦你开始运行,你可以通过将它包装在一个 Fiber.schedule 块中来使任何代码异步化

    Fiber.schedule do SynchronousCode.run end 

    整个库可以轻松地使用这种方法转换为异步,而且往往不需要比这里展示的更多努力。

    异步编程的重大好处是并行化阻塞/等待操作以减少程序运行时间。这通常意味着在单个 CPU 上运行更多的操作,或者更好地,在你的 Web 服务器上处理更多的请求。

    祝你使用纤程调度器( Fiber Scheduler )愉快!

    Happy hacking with Fiber Scheduler!

    目前尚无回复
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     921 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 30ms UTC 22:28 PVG 06:28 LAX 14:28 JFK 17:28
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86