【问题标题】:How to create a dedicated threadpool for CPU-intensive work in Tokio?如何在 Tokio 中为 CPU 密集型工作创建专用线程池?
【发布时间】:2020-08-28 08:42:57
【问题描述】:

我有一个基于 Tokio 运行时的 Rust 异步服务器。它必须同时处理对延迟敏感的 I/O 绑定请求和繁重的 CPU 绑定请求。

我不想让 CPU 密集型任务独占 Tokio 运行时并饿死 I/O 密集型任务,因此我想将 CPU 密集型任务卸载到一个专用的隔离线程池(隔离是关键在这里,所以一个共享线程池上的spawn_blocking/block_in_place 是不够的)。如何在 Tokio 中创建这样的线程池?

启动两个运行时的幼稚方法会遇到错误:

线程“tokio-runtime-worker”在“无法从运行时内启动运行时”出现恐慌。发生这种情况是因为一个函数(如block_on)在线程被用于驱动异步任务时试图阻塞当前线程。'

use tokio; // 0.2.20

fn main() {
    let mut main_runtime = tokio::runtime::Runtime::new().unwrap();
    let cpu_pool = tokio::runtime::Builder::new().threaded_scheduler().build().unwrap();
    let cpu_pool = cpu_pool.handle().clone(); // this is the fix/workaround!

    main_runtime.block_on(main_runtime.spawn(async move {
        cpu_pool.spawn(async {}).await
    }))
    .unwrap().unwrap();
}

Tokio 可以允许两个独立的运行时吗?有没有更好的方法在 Tokio 中创建一个隔离的 CPU 池?

【问题讨论】:

  • 我知道block_in_place,但它不能提供我正在寻找的隔离保证。
  • 我正在寻找的隔离保证,这是什么保证?请edit您的问题说明所有您的要求。
  • @Shepmaster 我认为它已经存在:“我不想让 CPU 密集型任务独占 Tokio 运行时并饿死 I/O 密集型任务。”

标签: rust threadpool rust-tokio


【解决方案1】:

虽然 Tokio 已经有一个线程池,但 documentation of Tokio advises

如果您的代码受 CPU 限制并且您希望限制用于运行它的线程数,您应该在另一个线程池上运行它,例如 rayon。当rayon 任务完成时,您可以使用oneshot 通道将结果发送回Tokio。

所以,如果你想创建一个线程池来大量使用 CPU,一个好方法是使用像 Rayon 这样的 crate 并将结果发送回 Tokio 任务。

【讨论】:

  • spawn_blocking 同样表示:要仅在几个线程上运行 CPU 密集型计算,您应该使用单独的线程池,例如 rayon,而不是配置阻塞线程的数量。
  • 使用人造丝需要通过异步通道发回结果,相当麻烦;(
  • @Kornel 我不认为频道很麻烦,但是与 tokio 有什么区别?我认为 tokio 生成的东西非常相似。您需要一种方法来发回结果。不要忘记,多线程的目的是使用更多的 CPU 来做更多的工作,但是开销是存在的。关键是人们应该只在这种过热情况下才使用多线程。
【解决方案2】:

启动 Tokio 运行时已经创建了一个线程池。相关选项是

粗略地说,core_threads 控制将使用多少线程来处理异步代码。 max_threads - core_threads 是多少线程将用于阻塞工作(强调我的):

否则,由于core_threads 始终处于活动状态,它会将其他线程(例如用于阻塞注释)限制为max_threads - core_threads

您还可以通过tokio::main 属性指定这些选项。

然后您可以使用以下任一方式注释阻塞代码:

另见:

spawn_blocking 可以轻松获取唯一运行时中的所有可用线程,从而迫使其他未来等待它们

在这种情况下,您可以使用Semaphore 之类的技术来限制最大并行度。

【讨论】:

  • 这不能回答我的问题。我正在寻找一个 dedicated 线程池,它不与我的主运行时共享线程。我已经在使用block_in_place,但这会破坏我的延迟并且阻塞的线程会导致网络连接饥饿。
  • @Kornel 也请解释一下为什么spawn_blocking 没有实现目标?
  • 因为我有很多很长的 CPU 密集型任务,所以spawn_blocking 可以轻松地在一个唯一的运行时占用所有可用的线程,从而迫使其他未来等待它们。
【解决方案3】:

Tokio 的错误消息was misleading。问题是由于 Runtime 对象在异步上下文中被丢弃

解决方法是使用Handle,而不是直接使用Runtime,在另一个运行时生成任务。

fn main() {
    let mut main_runtime = tokio::runtime::Runtime::new().unwrap();
    let cpu_pool = tokio::runtime::Builder::new().threaded_scheduler().build().unwrap();

    // this is the fix/workaround:
    let cpu_pool = cpu_pool.handle().clone(); 

    main_runtime.block_on(main_runtime.spawn(async move {
        cpu_pool.spawn(async {}).await
    }))
    .unwrap().unwrap();
}

【讨论】:

  • 我看不到在您的上下文中创建另一个运行时的意义。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-29
  • 1970-01-01
  • 1970-01-01
  • 2017-04-10
  • 1970-01-01
相关资源
最近更新 更多