Ruby 3.0中的大杀器 fiber线程调度

Fiber Scheduler 支持在 Ruby 中进行异步编程。该功能是 Ruby 3.0 的重要新增功能之一,也是令人敬畏的 async gem的核心组件之一。

最好的部分是您不需要整个框架即可开始!使用独立的 Fiber Scheduler 和几个内置的Ruby 方法就可以实现异步编程的好处。

Fiber Scheduler由两部分组成:

光纤调度器接口语言内置的一组用于阻塞操作的钩子。钩子实现被委托给Fiber.scheduler对象。光纤调度器实现实现异步行为。这是一个需要程序员明确设置的对象,因为 Ruby 不提供默认的 Fiber Scheduler 实现。

非常感谢塞缪尔·威廉姆斯!他是一名 Ruby 核心开发人员,他设计并在语言中实现了 Fiber Scheduler 功能。

光纤调度器接口

Fiber Scheduler 接口是一组用于阻塞操作的钩子。它允许在发生阻塞操作时插入异步行为。这就像带有扭曲的回调:当异步回调执行时,主阻塞方法不会运行

这些钩子记录在Fiber::SchedulerInterface 类中。这个 Ruby 特性背后的一些主要思想是:

  • 钩子是低级的。这导致了少量的钩子,每个钩子处理许多高级方法的行为。例如,#address_resolve钩子负责处理大约 20 个方法。
  • 挂钩仅在Fiber.scheduler设置对象时才起作用,并且挂钩的实现委托给该对象。
  • Hooks 的行为应该是异步的。

挂钩hook实现

让我们看一下如何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

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

阻塞操作

“阻塞操作”这个概念已经被提到过几次了,但它的真正含义是什么?阻塞操作是 Ruby 进程(更具体地说:当前线程)最终等待的任何操作。阻塞操作的更具描述性的名称是“等待操作”。

一些例子是:

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

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

def fibonacci(n)
  return n if [0, 1].include? n

  fibonacci(n - 1) + fibonacci(n - 2)
end

fibonacci(100)

得到结果fibonacci(100)需要很多等待,但等待的只是程序员!Ruby 解释器一直在工作,在后台处理数字。一个朴素的斐波那契实现不包含阻塞操作。

对阻塞操作是(和不是)有什么直觉是值得的,因为异步编程的重点是同时等待多个阻塞操作

光纤调度器实现

该实现是 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_schedulerFiberScheduler gem 中的类,它由您真正维护。这个 gem不是示例的硬依赖,因为如果将引用替换为另一个 Fiber Scheduler 类,下面的每个片段都应该仍然有效。FiberScheduler

基本示例

这是一个简单的例子:

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 方法,可以启动新的异步纤程。

该示例仅使用标准的 Ruby 方法——这两种方法自 Ruby 3.0Fiber.set_schedulerFiber.schedule就已可用。

高级示例

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

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 握手等的开销,执行时间会更长。

异步编程的主要好处之一是同时等待许多阻塞操作。随着阻塞操作数量的增加,好处也会增加。幸运的是,运行大量光纤非常容易。

结论

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 进行愉快的编程!

线程   Ruby   fiber
发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章