V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Mark24
V2EX  ›  Ruby

[翻译] Ruby Fiber Scheduler

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

    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   ·   实用小工具   ·   2922 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 06:17 · PVG 14:17 · LAX 22:17 · JFK 01:17
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.