[翻译] Ruby Fiber Scheduler

212 天前
 Mark24

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

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

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

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

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

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

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

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

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 秒内完成。

这个示例仅使用了标准的 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.schedule 块中来使任何代码异步化

Fiber.schedule do
  SynchronousCode.run
end

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

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

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

Happy hacking with Fiber Scheduler!

426 次点击
所在节点    Ruby
0 条回复

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/981356

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX