Rust运行时指南(官方文档翻译)

Rust运行时指南(官方文档翻译)

A Guide to the Rust Runtime, by Alex Crichton and Brian Anderson

翻译:庄晓立(Liigo),com.liigo@gmail.com,,Weibo,CSDN,Rust中文圈

日期:2014年2月。

Rust编程语言的标准发行版包含两个运行时库(libgreen和libnative),提供I/O等基础设施的统一接口。但对Rust语言本身而言,运行时(runtime)并不是必需的;Rust编译器可以生成在所有环境中运行的代码,包括内核(kernel)环境。Rust语言也不需要运行时提供内存安全,因为它的类型系统本身已经足够安全——通过编译时静态验证给予保证。运行时只是利用语言的安全特性提供一系列便利的、安全的、高层的抽象。

如果Rust没有运行时(runtime),我们编程能做的事情非常有限,所以Rust需要提供运行时。这份指导手册将探讨Rust用户空间(user-space)的运行时、如何使用它、它能做什么。

1、什么是运行时?

Rust运行时可以被视为提供以下功能代码的组合:输入/输出(I/O)、任务孵化(task spawning)、任务本地存储(TLS)等等。本质上,它提供一少部分对象,为实现常见功能提供便利支持。Rust运行时自身的实现是自包含的(self-contained),避免干扰其他库。

运行时目前提供以下功能特性(不完整列表):

1.1、运行时的目标是什么?

运行时的设计初衷是达成以下目标:

2、运行时的体系结构

本节将介绍目前的Rust运行时的体系结构。Rust运行时曾经被重写了几遍,本节仅涉及当前最新版本。

2.1、本地任务(a local task)

Rust运行时的核心抽象概念是任务(Task)。任务代表了运行Rust代码的“线程”,但此“线程”并不一定直接对应于操作系统里的线程。运行时里的大多数服务都是通过Task提供给用户,因而可以做到单个任务内部决策。

采用这种策略的结果是,要求所有使用标准库的Rust代码,都有一个本地的Task结构体(local Task structure)。该结构体被存储在操作系统的线程局部存储(TLS, Thread Local Storage)内部,以便高效访问。

一定有这么一个Task结构体存在,是Rust运行时本质上唯一的假定。这是一个核心假定,令所有使用标准库的代码受益,因此Task被定义在标准库内。几乎所有运行时服务都是通过Task提供的。

2.2、输入/输出(I/O)

当处理I/O时,通常有一些约定俗成的方法,但这些方法未必在任何情况下都正确。I/O的处理非常复杂,几乎不可能在多种环境中使用一致的处理方案。不能保证Rust任务(Task)有权限处理I/O,也不能保证它以何种方式处理I/O。

这意味着,标准库中无法定义处理I/O功能的具体实现代码,只能定义一批I/O操作接口,由各环境下的Task各自实现具体功能。这些I/O接口被设计为以同步I/O调用为核心。此架构不会根本性的阻碍以其他形式处理I/O,但目前还没有别的处理方式。

运行时(runtime)必须实现的这些I/O接口被定义在std::rt::rtio模块内。注意这些接口是不稳定的,是不对用户公开的(仅作为标准库内部实现细节)。

(译者Liigo注:任务的接口被定义在标准库libstd中,,任务的具体实现被定义在运行时库libgreen/libnative中。)

2.3、任务孵化(Task spawning)

任务(Task)的一项常见操作是,孵化(spawning)一个子任务(child task),在其中执行某些工作。这意味着并行执行被启用。如何孵化子任务,没有一个统一方法(未在标准库中定义),由各(运行时内的)任务自行决定。

任务孵化被解释为“孵化一个子任务(原文为sibling,疑为child之笔误)”,其高层操作接口定义在std::task模块中。孵化子任务前,可以事先设定子任务的参数,运行时的实现必须依据这些参数,执行具体的孵化行为。

任务的另一个操作是处理自身的运行状态,如阻塞(block)和唤醒(wake up)任务。具体操作细节由任务自己决定,标准库未做规定。

2.4、运行时接口(trait Runtime)和任务结构体(struct Task)

运行时的所有特性都被定义在接口Runtime和结构体Task。在不同运行时库(libgreen、libnative)中,结构体Task都是相同的,而接口Runtime的实现各不相同。Task内部存储了Runtime接口的实现对象,因而可以调用其接口函数。

3、运行时的实现(implementations)

Rust发行版提供了两个运行时库,分别是1:1线程模型的libnative和M:N线程模型的libgreen。就像许多计算机科学问题一样,你很难说选择哪个运行时库是正确的,它们各自都有优势和劣势。下面分别介绍两个运行时库提供的功能和没有的功能,供程序员参考并自行决定选择使用哪一个。

3.1、1:1 – 使用libnative

libnative运行时库的实现,是基于操作系统本地线程,加上libc阻塞I/O调用。因其用户空间的线程一一对应于操作系统线程,而被称为1:1线程模型。

在这种模型下,每一个Rust任务(Task)对应于一个操作系统线程,并且每一个I/O对象唯一对应一个文件描述符(fd)(或者其他系统内的对等物)。

使用libnative的一些优势:

保证与FFI绑定(外部函数接口绑定)交互操作。即使你调用的外部C库函数(例如数据库驱动)阻塞在线程I/O,也不会干扰其他Rust任务(Task)正常执行(因为它们在不同的系统线程内)。

在某些情况下相比M:N有更少的I/O损耗。并非所有M:N I/O都保证尽最大可能的快,而且有些东西(比如文件系统API)在某些平台下不是真正的异步操作,意味着M:N实现可能会比1:1实现引发更多损耗。

3.2、M:N – 使用libgreen

运行时库libgreen基于异步I/O框架libuv实现了“绿色线程”。M:N线程模型中的M是指当前进程内的操作系统本地线程个数,N是指Rust任务个数(即绿色线程个数)。在这种模型中,M个系统线程调度运行N个Rust绿色线程,在用户空间中进行线程上下文切换(context switching)。(译者Liigo注:通常情况下,N远大于M。)

M:N模型中很重要的一点是,Rust任务不能使用同步系统调用,阻塞任务自身。一旦被阻塞,任务所属的系统本地线程就整个僵化,无法再运行其他Rus任务。这意味着M个本地线程被(暂时)废掉了一个(但还有M-1个本地线程继续工作,因而能够做到0死锁)。通过在底层调用异步I/O接口(但从用户使用的角度看仍然像同步接口),系统本地线程永远不会阻塞。

libgreen库没有任何I/O实现,仅实现了Rust绿色线程的调度器(Schedulers)。真正的I/O实现位于libuv的封装库librustuv中。这么做的目的是希望将来会有不依赖libuv的I/O实现版本(当然目前还没有)。

使用libgreen的一些优势:

任务孵化的速度快。在M:N模型中,孵化新任务(绿色线程)时可以完全避免系统调用,效率更高。

因为有梦,所以勇敢出发,选择出发,便只顾风雨兼程。

Rust运行时指南(官方文档翻译)

相关文章:

你感兴趣的文章:

标签云: