# Ruby Fiber Scheduler(Ruby 纤程调度器)

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 伪代码。

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(等待操作)"。 一些例子如下:

  • sleep 方法。
  • I/O操作如 URI.open("https://brunosutic.com")
  • 系统命令,例如 curl https://www.ruby-lang.org
  • 通过 Thread#join 等待线程结束。

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

ruby 复制代码
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 类,每个代码片段仍然应该可以工作。

基本示例

这里有一个简单的示例:

ruby 复制代码
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 版本以来就一直可用。

高级例子

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

ruby 复制代码
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 实现的阻塞操作都可以工作!

扩展示例

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

ruby 复制代码
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 块中来使任何代码异步化

ruby 复制代码
Fiber.schedule do
  SynchronousCode.run
end

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

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

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

Happy hacking with Fiber Scheduler!

相关推荐
来一杯龙舌兰1 天前
【RabbitMQ】RabbitMQ保证消息不丢失的N种策略的思想总结
分布式·rabbitmq·ruby·持久化·ack·消息确认
明志-4 天前
RabbitMQ 工作模式使用案例之(发布订阅模式、路由模式、通配符模式)
分布式·rabbitmq·ruby
Agnoni7 天前
RabbitMQ消息队列的笔记
java·笔记·spring cloud·rabbitmq·ruby
飞的肖7 天前
RabbitMQ 安装、配置和使用介绍 使用前端js直接调用方式
开发语言·javascript·ruby
破局缘7 天前
apt文件问题ruby.list文件
开发语言·windows·ruby
Elastic 中国社区官方博客10 天前
如何将你的 Ruby 应用程序从 OpenSearch 迁移到 Elasticsearch
大数据·开发语言·数据库·后端·elasticsearch·搜索引擎·ruby
PGCCC11 天前
【PGCCC】 pg_query 6.0:使用 Postgres 自己的解析器解析、反解析和规范化 SQL 查询的 Ruby 库
数据库·sql·ruby
爱lv行11 天前
使用 rbenv 切换 Ruby 版本
开发语言·前端·ruby
信徒_14 天前
Rabbitmq 镜像队列
分布式·rabbitmq·ruby
姜西西_16 天前
RabbitMQ核心概念及工作流程 + AMQP
分布式·rabbitmq·ruby