You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trpl-zh-cn/ch17-02-concurrency-with-as...

615 lines
50 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>并发与 async - Rust 程序设计语言 简体中文版</title>
<!-- Custom HTML head -->
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="favicon-de23e50b.svg">
<link rel="shortcut icon" href="favicon-8114d1fc.png">
<link rel="stylesheet" href="css/variables-8adf115d.css">
<link rel="stylesheet" href="css/general-2459343d.css">
<link rel="stylesheet" href="css/chrome-ae938929.css">
<link rel="stylesheet" href="css/print-9e4910d8.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="fonts/fonts-9644e21d.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" id="mdbook-highlight-css" href="highlight-493f70e1.css">
<link rel="stylesheet" id="mdbook-tomorrow-night-css" href="tomorrow-night-4c0ae647.css">
<link rel="stylesheet" id="mdbook-ayu-highlight-css" href="ayu-highlight-3fdfc3ac.css">
<!-- Custom theme stylesheets -->
<link rel="stylesheet" href="ferris-d33b75bf.css">
<link rel="stylesheet" href="theme/2018-edition-4e126c62.css">
<link rel="stylesheet" href="theme/semantic-notes-9b5766c0.css">
<link rel="stylesheet" href="theme/listing-cab26221.css">
<!-- Provide site root and default themes to javascript -->
<script>
const path_to_root = "";
const default_light_theme = "light";
const default_dark_theme = "navy";
window.path_to_searchindex_js = "searchindex-e0ffcbc3.js";
</script>
<!-- Start loading toc.js asap -->
<script src="toc-fb31ca2f.js"></script>
</head>
<body>
<div id="mdbook-help-container">
<div id="mdbook-help-popup">
<h2 class="mdbook-help-title">Keyboard shortcuts</h2>
<div>
<p>Press <kbd></kbd> or <kbd></kbd> to navigate between chapters</p>
<p>Press <kbd>S</kbd> or <kbd>/</kbd> to search in the book</p>
<p>Press <kbd>?</kbd> to show this help</p>
<p>Press <kbd>Esc</kbd> to hide this help</p>
</div>
</div>
</div>
<div id="mdbook-body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
let theme = localStorage.getItem('mdbook-theme');
let sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
const default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? default_dark_theme : default_light_theme;
let theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('light')
html.classList.add(theme);
html.classList.add("js");
</script>
<input type="checkbox" id="mdbook-sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
let sidebar = null;
const sidebar_toggle = document.getElementById("mdbook-sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
sidebar_toggle.checked = false;
}
if (sidebar === 'visible') {
sidebar_toggle.checked = true;
} else {
html.classList.remove('sidebar-visible');
}
</script>
<nav id="mdbook-sidebar" class="sidebar" aria-label="Table of contents">
<!-- populated by js -->
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="toc.html"></iframe>
</noscript>
<div id="mdbook-sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<div id="mdbook-page-wrapper" class="page-wrapper">
<div class="page">
<div id="mdbook-menu-bar-hover-placeholder"></div>
<div id="mdbook-menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="mdbook-sidebar-toggle" class="icon-button" for="mdbook-sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="mdbook-sidebar">
<span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M0 96C0 78.3 14.3 64 32 64H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H416c17.7 0 32 14.3 32 32z"/></svg></span>
</label>
<button id="mdbook-theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="mdbook-theme-list">
<span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M371.3 367.1c27.3-3.9 51.9-19.4 67.2-42.9L600.2 74.1c12.6-19.5 9.4-45.3-7.6-61.2S549.7-4.4 531.1 9.6L294.4 187.2c-24 18-38.2 46.1-38.4 76.1L371.3 367.1zm-19.6 25.4l-116-104.4C175.9 290.3 128 339.6 128 400c0 3.9 .2 7.8 .6 11.6c1.8 17.5-10.2 36.4-27.8 36.4H96c-17.7 0-32 14.3-32 32s14.3 32 32 32H240c61.9 0 112-50.1 112-112c0-2.5-.1-5-.2-7.5z"/></svg></span>
</button>
<ul id="mdbook-theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="mdbook-theme-default_theme">Auto</button></li>
<li role="none"><button role="menuitem" class="theme" id="mdbook-theme-light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="mdbook-theme-rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="mdbook-theme-coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="mdbook-theme-navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="mdbook-theme-ayu">Ayu</button></li>
</ul>
<button id="mdbook-search-toggle" class="icon-button" type="button" title="Search (`/`)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="/ s" aria-controls="mdbook-searchbar">
<span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352c79.5 0 144-64.5 144-144s-64.5-144-144-144S64 128.5 64 208s64.5 144 144 144z"/></svg></span>
</button>
</div>
<h1 class="menu-title">Rust 程序设计语言 简体中文版</h1>
<div class="right-buttons">
<a href="print.html" title="Print this book" aria-label="Print this book">
<span class=fa-svg id="print-button"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M128 0C92.7 0 64 28.7 64 64v96h64V64H354.7L384 93.3V160h64V93.3c0-17-6.7-33.3-18.7-45.3L400 18.7C388 6.7 371.7 0 354.7 0H128zM384 352v32 64H128V384 368 352H384zm64 32h32c17.7 0 32-14.3 32-32V256c0-35.3-28.7-64-64-64H64c-35.3 0-64 28.7-64 64v96c0 17.7 14.3 32 32 32H64v64c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V384zm-16-88c-13.3 0-24-10.7-24-24s10.7-24 24-24s24 10.7 24 24s-10.7 24-24 24z"/></svg></span>
</a>
<a href="https://github.com/KaiserY/trpl-zh-cn/tree/main" title="Git repository" aria-label="Git repository">
<span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg></span>
</a>
</div>
</div>
<div id="mdbook-search-wrapper" class="hidden">
<form id="mdbook-searchbar-outer" class="searchbar-outer">
<div class="search-wrapper">
<input type="search" id="mdbook-searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="mdbook-searchresults-outer" aria-describedby="searchresults-header">
<div class="spinner-wrapper">
<span class=fa-svg id="fa-spin"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M304 48c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48zm0 416c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48zM48 304c26.5 0 48-21.5 48-48s-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48zm464-48c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48zM142.9 437c18.7-18.7 18.7-49.1 0-67.9s-49.1-18.7-67.9 0s-18.7 49.1 0 67.9s49.1 18.7 67.9 0zm0-294.2c18.7-18.7 18.7-49.1 0-67.9S93.7 56.2 75 75s-18.7 49.1 0 67.9s49.1 18.7 67.9 0zM369.1 437c18.7 18.7 49.1 18.7 67.9 0s18.7-49.1 0-67.9s-49.1-18.7-67.9 0s-18.7 49.1 0 67.9z"/></svg></span>
</div>
</div>
</form>
<div id="mdbook-searchresults-outer" class="searchresults-outer hidden">
<div id="mdbook-searchresults-header" class="searchresults-header"></div>
<ul id="mdbook-searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('mdbook-sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('mdbook-sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#mdbook-sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="mdbook-content" class="content">
<main>
<h2 id="使用-async-实现并发"><a class="header" href="#使用-async-实现并发">使用 async 实现并发</a></h2>
<p><a href="https://github.com/rust-lang/book/blob/f78ab89d7545ac17780e6a367055cc089f4cd2ec/src/ch17-02-concurrency-with-async.md">ch17-02-concurrency-with-async.md</a></p>
<p>在这一部分,我们将使用异步来应对一些与第十六章中通过线程解决的相同的并发问题。因为之前我们已经讨论了很多关键理念了,这一部分我们会专注于线程与 future 的区别。</p>
<p>在很多情况下,使用异步处理并发的 API 与使用线程的非常相似。在其它的一些情况,它们则非常不同。即便线程与异步的 API <em>看起来</em> 很类似,通常它们有着不同的行为,同时它们几乎总是有着不同的性能特点。</p>
<h3 id="使用-spawn_task-创建新任务"><a class="header" href="#使用-spawn_task-创建新任务">使用 <code>spawn_task</code> 创建新任务</a></h3>
<p>第十六章中我们应付的第一个任务是在两个不同的线程中计数。让我们用异步来完成相同的任务。<code>trpl</code> crate 提供了一个 <code>spawn_task</code> 函数,它看起来非常像 <code>thread::spawn</code> API和一个 <code>sleep</code> 函数,这是 <code>thread::sleep</code> API 的异步版本。我们可以将它们结合使用,实现与线程示例相同的计数功能,如示例 17-6 所示。</p>
<figure class="listing">
<p><span class="file-name">文件名src/main.rs</span></p>
<pre class="playground"><code class="language-rust edition2024"><span class="boring">extern crate trpl; // required for mdbook test
</span><span class="boring">
</span>use std::time::Duration;
fn main() {
trpl::block_on(async {
trpl::spawn_task(async {
for i in 1..10 {
println!("hi number {i} from the first task!");
trpl::sleep(Duration::from_millis(500)).await;
}
});
for i in 1..5 {
println!("hi number {i} from the second task!");
trpl::sleep(Duration::from_millis(500)).await;
}
});
}</code></pre>
<figcaption>示例 17-6创建一个新任务在主任务打印内容的同时打印另一组内容</figcaption>
</figure>
<p>作为起点,我们在 <code>main</code> 函数中使用 <code>trpl::block_on</code>,这样顶层函数就可以写成 async 风格。</p>
<blockquote>
<p>注意:从这里开始,本章中的每个示例在 <code>main</code> 中都会包含这段几乎完全一样的 <code>trpl::block_on</code> 包装代码,所以之后我们通常会像省略 <code>main</code> 一样把它省掉。记得在你自己的代码里补上它!</p>
</blockquote>
<p>然后我们在这个代码块里写了两个循环,每个循环中都调用了 <code>trpl::sleep</code>在输出下一条消息之前等待半秒500 毫秒)。其中一个循环放在 <code>trpl::spawn_task</code> 的函数体里,另一个则放在顶层的 <code>for</code> 循环中。我们还在 <code>sleep</code> 调用后加上了 <code>await</code></p>
<p>这段代码的行为和线程版实现很像,包括当你亲自运行时,终端中的消息顺序可能和这里不完全一样:</p>
<!-- Not extracting output because changes to this output aren't significant;
the changes are likely to be due to the threads running differently rather than
changes in the compiler -->
<pre><code class="language-text">hi number 1 from the second task!
hi number 1 from the first task!
hi number 2 from the first task!
hi number 2 from the second task!
hi number 3 from the first task!
hi number 3 from the second task!
hi number 4 from the first task!
hi number 4 from the second task!
hi number 5 from the first task!
</code></pre>
<p>这个版本会在主 async 块中的 <code>for</code> 循环一结束就停止,因为当 <code>main</code> 函数结束时,由 <code>spawn_task</code> 生成的任务也会被关闭。如果你想让它一直运行到任务自身完成,就需要使用 join handle 来等待第一个任务结束。在线程的版本中,我们使用 <code>join</code> 方法“阻塞”等待线程运行结束。在示例 17-7 中,我们可以使用 <code>await</code> 做同样的事,因为任务句柄本身就是一个 future。它的 <code>Output</code> 类型是 <code>Result</code>,所以在等待之后还要再 <code>unwrap</code> 一次。</p>
<figure class="listing">
<p><span class="file-name">文件名src/main.rs</span></p>
<pre class="playground"><code class="language-rust edition2024"><span class="boring">extern crate trpl; // required for mdbook test
</span><span class="boring">
</span><span class="boring">use std::time::Duration;
</span><span class="boring">
</span><span class="boring">fn main() {
</span><span class="boring"> trpl::block_on(async {
</span> let handle = trpl::spawn_task(async {
for i in 1..10 {
println!("hi number {i} from the first task!");
trpl::sleep(Duration::from_millis(500)).await;
}
});
for i in 1..5 {
println!("hi number {i} from the second task!");
trpl::sleep(Duration::from_millis(500)).await;
}
handle.await.unwrap();
<span class="boring"> });
</span><span class="boring">}</span></code></pre>
<figcaption>示例 17-7在 join 句柄上使用 `await`,让任务运行到完成</figcaption>
</figure>
<p>更新后的版本会一直运行到<em>两个</em>循环都完成为止:</p>
<!-- Not extracting output because changes to this output aren't significant;
the changes are likely to be due to the threads running differently rather than
changes in the compiler -->
<pre><code class="language-text">hi number 1 from the second task!
hi number 1 from the first task!
hi number 2 from the first task!
hi number 2 from the second task!
hi number 3 from the first task!
hi number 3 from the second task!
hi number 4 from the first task!
hi number 4 from the second task!
hi number 5 from the first task!
hi number 6 from the first task!
hi number 7 from the first task!
hi number 8 from the first task!
hi number 9 from the first task!
</code></pre>
<p>到目前为止,看起来 async 和线程只是用不同语法实现了相似效果:在 join handle 上使用 <code>await</code>,而不是调用 <code>join</code>;同时对 <code>sleep</code> 调用也使用 <code>await</code></p>
<p>更大的不同在于,我们根本不需要再创建另一个操作系统线程来做这件事。实际上,这里甚至连任务都不一定要创建。因为 async 代码块会被编译成匿名 future我们可以把每个循环都放进一个 async 代码块里,然后让运行时使用 <code>trpl::join</code> 让它们都执行到完成。</p>
<p>在第十六章<a href="ch16-01-threads.html#等待所有线程结束">“等待所有线程完成”</a>一节中,我们展示了如何对 <code>std::thread::spawn</code> 返回的 <code>JoinHandle</code> 调用 <code>join</code> 方法。<code>trpl::join</code> 与之类似,不过它面向的是 future。当你把两个 future 传给它时,它会生成一个新的 future等到<em>两个</em>传入的 future 都完成时,这个新 future 的输出就是一个包含它们各自输出值的元组。因此,在示例 17-8 中,我们用 <code>trpl::join</code> 来等待 <code>fut1</code><code>fut2</code> 完成。我们<em>不会</em>分别等待 <code>fut1</code><code>fut2</code>,而是等待 <code>trpl::join</code> 生成的那个新 future。这里我们忽略它的输出因为那不过是一个包含两个 unit 值的元组。</p>
<figure class="listing">
<p><span class="file-name">文件名src/main.rs</span></p>
<pre class="playground"><code class="language-rust edition2024"><span class="boring">extern crate trpl; // required for mdbook test
</span><span class="boring">
</span><span class="boring">use std::time::Duration;
</span><span class="boring">
</span><span class="boring">fn main() {
</span><span class="boring"> trpl::block_on(async {
</span> let fut1 = async {
for i in 1..10 {
println!("hi number {i} from the first task!");
trpl::sleep(Duration::from_millis(500)).await;
}
};
let fut2 = async {
for i in 1..5 {
println!("hi number {i} from the second task!");
trpl::sleep(Duration::from_millis(500)).await;
}
};
trpl::join(fut1, fut2).await;
<span class="boring"> });
</span><span class="boring">}</span></code></pre>
<figcaption>示例 17-8使用 `trpl::join` 等待两个匿名 future</figcaption>
</figure>
<p>运行后,我们会看到两个 future 都执行到了结束:</p>
<!-- Not extracting output because changes to this output aren't significant;
the changes are likely to be due to the threads running differently rather than
changes in the compiler -->
<pre><code class="language-text">hi number 1 from the first task!
hi number 1 from the second task!
hi number 2 from the first task!
hi number 2 from the second task!
hi number 3 from the first task!
hi number 3 from the second task!
hi number 4 from the first task!
hi number 4 from the second task!
hi number 5 from the first task!
hi number 6 from the first task!
hi number 7 from the first task!
hi number 8 from the first task!
hi number 9 from the first task!
</code></pre>
<p>现在你会发现,每次运行时顺序都完全一样,这和线程版本以及示例 17-7 中使用 <code>trpl::spawn_task</code> 的情况非常不同。这是因为 <code>trpl::join</code><em>fair</em> 的,也就是它会以同样的频率检查每一个 future在它们之间交替进行只要另一个 future 已经就绪,它就不会让其中一个一路领先。在线程模型下,由操作系统决定先检查哪个线程、让它运行多久。对于 async Rust则由运行时决定先检查哪个任务。在实践中细节会复杂得多因为异步运行时可能会在底层借助操作系统线程来实现并发因此要保证公平性对运行时来说可能意味着更多工作但这仍然是可能做到的。运行时并不一定会为任何给定操作都保证公平性而且它们通常会提供不同的 API让你自行决定是否需要公平性。</p>
<p>尝试这些不同的 await future 的变体来观察它们的效果:</p>
<ul>
<li>去掉一个或者两个循环外的异步代码块。</li>
<li>在定义两个异步代码块后立刻 await 它们。</li>
<li>只将第一个循环封装进异步代码块,并在第二个循环体之后 await 作为结果的 future。</li>
</ul>
<p>作为额外的挑战,看看你能否在运行代码 <em>之前</em> 想出每个情况下的输出!</p>
<!-- Old headings. Do not remove or links may break. -->
<p><a id="message-passing"></a>
<a id="counting-up-on-two-tasks-using-message-passing"></a></p>
<h3 id="通过消息传递在两个任务之间发送数据"><a class="header" href="#通过消息传递在两个任务之间发送数据">通过消息传递在两个任务之间发送数据</a></h3>
<p>在 future 之间共享数据的方式也会让你感到熟悉:我们再次使用消息传递,只不过这次使用的是异步版本的类型和函数。为了展示基于线程的并发和基于 future 的并发之间的一些关键差别,我们会和第十六章<a href="ch16-02-message-passing.html">“通过消息传递在线程间传送数据”</a>一节稍微走一条不一样的路线。在示例 17-9 中,我们先只使用一个 async 代码块,而<em></em>像之前那样显式地创建一个独立任务。</p>
<figure class="listing">
<p><span class="file-name">文件名src/main.rs</span></p>
<pre class="playground"><code class="language-rust edition2024"><span class="boring">extern crate trpl; // required for mdbook test
</span><span class="boring">
</span><span class="boring">fn main() {
</span><span class="boring"> trpl::block_on(async {
</span> let (tx, mut rx) = trpl::channel();
let val = String::from("hi");
tx.send(val).unwrap();
let received = rx.recv().await.unwrap();
println!("received '{received}'");
<span class="boring"> });
</span><span class="boring">}</span></code></pre>
<figcaption>示例 17-9创建一个异步信道async channel并赋值其两端为 `tx` 和 `rx`</figcaption>
</figure>
<p>这里我们使用了 <code>trpl::channel</code>,一个第十六章用于线程的多生产者、单消费者信道 API 的异步版本。异步版本的 API 与基于线程的版本只有一点微小的区别:它使用一个可变的而不是不可变的 <code>rx</code>,并且它的 <code>recv</code> 方法产生一个需要 await 的 future 而不是直接返回值。现在我们可以发送端向接收端发送消息了。注意我们无需产生一个独立的线程或者任务只需等待await <code>rx.recv</code> 调用。</p>
<p><code>std::mpsc::channel</code> 中的同步 <code>Receiver::recv</code> 方法阻塞执行直到它接收一个消息。<code>trpl::Receiver::recv</code> 则不会阻塞,因为它是异步的。不同于阻塞,它将控制权交还给运行时,直到接收到一个消息或者信道的发送端关闭。相比之下,我们不用 await <code>send</code>,因为它不会阻塞。也无需阻塞,因为信道的发送端的数量是没有限制的。</p>
<blockquote>
<p>注意:因为这些 async 代码都运行在传给 <code>trpl::block_on</code> 的 async 代码块里,所以块中的所有内容都可以避免阻塞。不过,块<em>外部</em>的代码则会阻塞,直到 <code>block_on</code> 返回为止。这正是 <code>trpl::block_on</code> 的意义所在:它让你可以<em>选择</em>在哪一处对一组 async 代码进行阻塞,从而也就决定了在什么地方切换同步和异步代码。</p>
</blockquote>
<p>请注意这个示例中的两个地方:首先,消息立刻就会到达!其次,虽然我们使用了 future但是这里还没有并发。示例中的所有事情都是顺序发生的就像没涉及到 future 时一样。</p>
<p>让我们通过发送一系列消息并在之间休眠来解决第一个问题,如示例 17-10 所示:</p>
<figure class="listing">
<p><span class="file-name">文件名src/main.rs</span></p>
<pre class="playground"><code class="language-rust edition2024"><span class="boring">extern crate trpl; // required for mdbook test
</span><span class="boring">
</span><span class="boring">use std::time::Duration;
</span><span class="boring">
</span><span class="boring">fn main() {
</span><span class="boring"> trpl::block_on(async {
</span> let (tx, mut rx) = trpl::channel();
let vals = vec![
String::from("hi"),
String::from("from"),
String::from("the"),
String::from("future"),
];
for val in vals {
tx.send(val).unwrap();
trpl::sleep(Duration::from_millis(500)).await;
}
while let Some(value) = rx.recv().await {
println!("received '{value}'");
}
<span class="boring"> });
</span><span class="boring">}</span></code></pre>
<figcaption>示例 17-10通过异步信道发送和接收多个消息并在每个消息之间通过 `await` 休眠</figcaption>
</figure>
<p>除了发送消息之外,我们还需要接收它们。在这个例子中我们可以手动接收,就是调用四次 <code>rx.recv().await</code>,因为我们知道进来了多少条消息。然而,在现实世界中,我们通常会等待 <em>未知</em> 数量的消息。这时我们需要一直等待直到可以确认没有更多消息了为止。</p>
<p>在示例 16-10 中,我们使用 <code>for</code> 循环处理从同步信道接收到的所有条目。不过Rust 目前还没有办法对<em>异步产生的</em>一系列条目使用 <code>for</code> 循环。因此,我们需要一种前面还没见过的循环:<code>while let</code> 条件循环。它正是我们在第六章<a href="ch06-03-if-let.html">“使用 <code>if let</code><code>let...else</code> 实现简洁控制流”</a>中见过的 <code>if let</code> 结构的循环版本。只要它指定的模式还在持续匹配,循环就会继续执行。</p>
<p><code>rx.recv</code> 调用产生一个 <code>Future</code>,我们会 await 它。运行时会暂停 <code>Future</code> 直到它就绪。一旦消息到达future 会解析为 <code>Some(message)</code>,每次消息到达时都会如此。当信道关闭时,不管是否有 <em>任何</em> 消息到达future 都会解析为 <code>None</code> 来表明没有更多的值了,我们也就应该停止轮询,也就是停止等待。</p>
<p><code>while let</code> 循环将上述逻辑整合在一起。如果 <code>rx.recv().await</code> 调用的结果是 <code>Some(message)</code>,我们会得到消息并可以在循环体中使用它,就像使用 <code>if let</code> 一样。如果结果是 <code>None</code>,则循环停止。每次循环执行完毕,它会再次触发 await point如此运行时会再次暂停直到另一条消息到达。</p>
<p>现在代码可以成功发送和接收所有的消息了。不幸的是这里还有一些问题。首先消息并不是按照半秒的间隔到达的。它们在程序启动后两秒2000 毫秒)后立刻一起到达。其次,程序永远也不会退出!相反它会永远等待新消息。你会需要使用 <span class="keystroke">ctrl-c</span> 来关闭它。</p>
<h4 id="一个-async-代码块中的代码会线性执行"><a class="header" href="#一个-async-代码块中的代码会线性执行">一个 async 代码块中的代码会线性执行</a></h4>
<p>先来看为什么这些消息会在完整延迟之后一起到达,而不是在每次延迟之后逐条到达。在一个给定的 async 代码块里,代码中 <code>await</code> 出现的顺序,也就是程序运行时它们执行的顺序。</p>
<p>示例 17-10 中只有一个 async 代码块,所以里面的一切都按线性顺序执行。这里依然没有并发。所有 <code>tx.send</code> 调用,连同 <code>trpl::sleep</code> 调用及其相应的 await 点,都会先全部依次发生。只有在那之后,<code>while let</code> 循环才有机会开始执行 <code>recv</code> 调用上的那些 await 点。</p>
<p>为了得到我们真正想要的行为,也就是在每条消息之间都出现休眠间隔,我们需要把 <code>tx</code><code>rx</code> 的操作分别放进各自的 async 代码块中,如示例 17-11 所示。这样运行时就可以像示例 17-8 那样,使用 <code>trpl::join</code> 分别执行它们。我们再次等待的是 <code>trpl::join</code> 调用的结果,而不是分别等待每个 future。要是依次等待它们我们就又回到了顺序执行的流程这正是我们<em></em>想要的。</p>
<!-- We cannot test this one because it never stops! -->
<figure class="listing">
<p><span class="file-name">文件名src/main.rs</span></p>
<pre><code class="language-rust ignore"><span class="boring">extern crate trpl; // required for mdbook test
</span><span class="boring">
</span><span class="boring">use std::time::Duration;
</span><span class="boring">
</span><span class="boring">fn main() {
</span><span class="boring"> trpl::block_on(async {
</span><span class="boring"> let (tx, mut rx) = trpl::channel();
</span><span class="boring">
</span> let tx_fut = async {
let vals = vec![
String::from("hi"),
String::from("from"),
String::from("the"),
String::from("future"),
];
for val in vals {
tx.send(val).unwrap();
trpl::sleep(Duration::from_millis(500)).await;
}
};
let rx_fut = async {
while let Some(value) = rx.recv().await {
println!("received '{value}'");
}
};
trpl::join(tx_fut, rx_fut).await;
<span class="boring"> });
</span><span class="boring">}</span></code></pre>
<figcaption>示例 17-11将 `send` 和 `recv` 分隔到其各自的 `async` 代码块中并 await 这些代码块的 future</figcaption>
</figure>
<p>使用示例 17-11 中更新后的代码后,消息就会以 500 毫秒的间隔输出,而不是在 2 秒之后一次性全部打印出来。</p>
<h4 id="将所有权移入-async-代码块"><a class="header" href="#将所有权移入-async-代码块">将所有权移入 async 代码块</a></h4>
<p>但是程序仍然永远也不会退出,这是由于 <code>while let</code> 循环与 <code>trpl::join</code> 的交互方式所致:</p>
<ul>
<li><code>trpl::join</code> 返回的 future 只会完成一次,即传递的 <em>两个</em> future 都完成的时候。</li>
<li><code>tx_fut</code> future 会在发送完 <code>vals</code> 中最后一条消息后,再完成最后一次休眠之后结束。</li>
<li><code>rx_fut</code> future 则要等到 <code>while let</code> 循环结束时才会结束。</li>
<li>只有当等待 <code>rx.recv</code> 的结果变成 <code>None</code> 时,<code>while let</code> 循环才会结束。</li>
<li>只有在信道另一端关闭后,等待 <code>rx.recv</code> 才会返回 <code>None</code></li>
<li>只有在我们调用 <code>rx.close</code>,或者发送端 <code>tx</code> 被 drop 时,信道才会关闭。</li>
<li>我们根本没有调用 <code>rx.close</code>,而 <code>tx</code> 也要等到传给 <code>trpl::block_on</code> 的最外层 async 代码块结束后才会被 drop。</li>
<li>但那个最外层 async 代码块又必须等 <code>trpl::join</code> 完成才能结束,于是我们就又回到了这个列表的起点。</li>
</ul>
<p>目前,发送消息的那个 async 代码块只是<em>借用</em><code>tx</code>,因为发送消息并不需要取得它的所有权。但如果我们能把 <code>tx</code> <em>move</em> 进那个 async 代码块里,那么一旦该代码块结束,<code>tx</code> 就会被 drop。在第十三章<a href="ch13-01-closures.html#捕获引用或移动所有权">“捕获引用或移动所有权”</a>中,你学过如何在闭包上使用 <code>move</code> 关键字;而正如第十六章<a href="ch16-01-threads.html#将-move-闭包与线程一同使用">“将 <code>move</code> 闭包与线程一同使用”</a>一节提到的那样,在线程场景下我们也经常需要把数据 move 进闭包。相同的基本原理也适用于 async 代码块,因此 <code>move</code> 关键字同样可以和 async 代码块一起使用。</p>
<p>在示例 17-12 中,我们把发送消息用的代码块从 <code>async</code> 改为 <code>async move</code></p>
<figure class="listing">
<p><span class="file-name">文件名src/main.rs</span></p>
<pre class="playground"><code class="language-rust edition2024"><span class="boring">extern crate trpl; // required for mdbook test
</span><span class="boring">
</span><span class="boring">use std::time::Duration;
</span><span class="boring">
</span><span class="boring">fn main() {
</span><span class="boring"> trpl::block_on(async {
</span> let (tx, mut rx) = trpl::channel();
let tx_fut = async move {
// --snip--
<span class="boring"> let vals = vec![
</span><span class="boring"> String::from("hi"),
</span><span class="boring"> String::from("from"),
</span><span class="boring"> String::from("the"),
</span><span class="boring"> String::from("future"),
</span><span class="boring"> ];
</span><span class="boring">
</span><span class="boring"> for val in vals {
</span><span class="boring"> tx.send(val).unwrap();
</span><span class="boring"> trpl::sleep(Duration::from_millis(500)).await;
</span><span class="boring"> }
</span><span class="boring"> };
</span><span class="boring">
</span><span class="boring"> let rx_fut = async {
</span><span class="boring"> while let Some(value) = rx.recv().await {
</span><span class="boring"> println!("received '{value}'");
</span><span class="boring"> }
</span><span class="boring"> };
</span><span class="boring">
</span><span class="boring"> trpl::join(tx_fut, rx_fut).await;
</span><span class="boring"> });
</span><span class="boring">}</span></code></pre>
<figcaption>示例 17-12对示例 17-11 的修改版本,它会在完成后正确关闭</figcaption>
</figure>
<p>运行<em>这个</em>版本的代码后,它就会在最后一条消息发送并接收完之后正常退出。接下来,我们来看看,如果要从多个 future 发送数据,又需要做哪些变化。</p>
<h4 id="使用-join-宏合并多个-future"><a class="header" href="#使用-join-宏合并多个-future">使用 <code>join!</code> 宏合并多个 future</a></h4>
<p>这个异步信道同样也是多生产者信道,因此如果我们希望从多个 future 发送消息,就可以对 <code>tx</code> 调用 <code>clone</code>,如示例 17-13 所示。</p>
<figure class="listing">
<p><span class="file-name">文件名src/main.rs</span></p>
<pre class="playground"><code class="language-rust edition2024"><span class="boring">extern crate trpl; // required for mdbook test
</span><span class="boring">
</span><span class="boring">use std::time::Duration;
</span><span class="boring">
</span><span class="boring">fn main() {
</span><span class="boring"> trpl::block_on(async {
</span> let (tx, mut rx) = trpl::channel();
let tx1 = tx.clone();
let tx1_fut = async move {
let vals = vec![
String::from("hi"),
String::from("from"),
String::from("the"),
String::from("future"),
];
for val in vals {
tx1.send(val).unwrap();
trpl::sleep(Duration::from_millis(500)).await;
}
};
let rx_fut = async {
while let Some(value) = rx.recv().await {
println!("received '{value}'");
}
};
let tx_fut = async move {
let vals = vec![
String::from("more"),
String::from("messages"),
String::from("for"),
String::from("you"),
];
for val in vals {
tx.send(val).unwrap();
trpl::sleep(Duration::from_millis(1500)).await;
}
};
trpl::join!(tx1_fut, tx_fut, rx_fut);
<span class="boring"> });
</span><span class="boring">}</span></code></pre>
<figcaption>示例 17-13在 async 代码块中使用多个生产者</figcaption>
</figure>
<p>首先,我们克隆 <code>tx</code>,在第一个 async 代码块外创建出 <code>tx1</code>。然后像之前处理 <code>tx</code> 那样,把 <code>tx1</code> move 进这个代码块里。随后,我们再把原始的 <code>tx</code> move 进一个<em>新的</em> async 代码块,在那里以稍慢一点的节奏继续发送更多消息。这里我们把这个新 async 代码块放在接收消息的 async 代码块后面,不过放在前面也同样可以。关键在于 future 被等待的顺序,而不是它们被创建的顺序。</p>
<p>两个负责发送消息的 async 代码块都必须写成 <code>async move</code>,这样当代码块结束时,<code>tx</code><code>tx1</code> 都会被 drop。否则我们又会回到一开始那个无限循环的问题。</p>
<p>最后,我们从 <code>trpl::join</code> 切换为 <code>trpl::join!</code> 来处理新增的 future。<code>join!</code> 宏可以在 future 数量已知于编译期的情况下,等待任意数量的 future。本章稍后我们还会讨论如何等待一个数量事先未知的 future 集合。</p>
<p>现在我们就能看到来自两个发送 future 的所有消息了。由于这两个发送 future 在发送后使用了略微不同的延迟,接收到这些消息的时间间隔也会相应不同:</p>
<!-- Not extracting output because changes to this output aren't significant;
the changes are likely to be due to the threads running differently rather than
changes in the compiler -->
<pre><code class="language-text">received 'hi'
received 'more'
received 'from'
received 'the'
received 'messages'
received 'future'
received 'for'
received 'you'
</code></pre>
<p>我们已经探索了如何用消息传递在 future 之间发送数据、一个 async 代码块中的代码如何按顺序执行、如何将所有权 move 进 async 代码块,以及如何合并多个 future。接下来我们来讨论一下为什么以及如何告诉运行时它现在可以切换去执行别的任务了。</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="ch17-01-futures-and-syntax.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M41.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 246.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"/></svg></span>
</a>
<a rel="next prefetch" href="ch17-03-more-futures.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M278.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-160 160c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L210.7 256 73.4 118.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l160 160z"/></svg></span>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="ch17-01-futures-and-syntax.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M41.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 246.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"/></svg></span>
</a>
<a rel="next prefetch" href="ch17-03-more-futures.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M278.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-160 160c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L210.7 256 73.4 118.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l160 160z"/></svg></span>
</a>
</nav>
</div>
<template id=fa-eye><span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM432 256c0 79.5-64.5 144-144 144s-144-64.5-144-144s64.5-144 144-144s144 64.5 144 144zM288 192c0 35.3-28.7 64-64 64c-11.5 0-22.3-3-31.6-8.4c-.2 2.8-.4 5.5-.4 8.4c0 53 43 96 96 96s96-43 96-96s-43-96-96-96c-2.8 0-5.6 .1-8.4 .4c5.3 9.3 8.4 20.1 8.4 31.6z"/></svg></span></template>
<template id=fa-eye-slash><span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M38.8 5.1C28.4-3.1 13.3-1.2 5.1 9.2S-1.2 34.7 9.2 42.9l592 464c10.4 8.2 25.5 6.3 33.7-4.1s6.3-25.5-4.1-33.7L525.6 386.7c39.6-40.6 66.4-86.1 79.9-118.4c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C465.5 68.8 400.8 32 320 32c-68.2 0-125 26.3-169.3 60.8L38.8 5.1zM223.1 149.5C248.6 126.2 282.7 112 320 112c79.5 0 144 64.5 144 144c0 24.9-6.3 48.3-17.4 68.7L408 294.5c5.2-11.8 8-24.8 8-38.5c0-53-43-96-96-96c-2.8 0-5.6 .1-8.4 .4c5.3 9.3 8.4 20.1 8.4 31.6c0 10.2-2.4 19.8-6.6 28.3l-90.3-70.8zm223.1 298L373 389.9c-16.4 6.5-34.3 10.1-53 10.1c-79.5 0-144-64.5-144-144c0-6.9 .5-13.6 1.4-20.2L83.1 161.5C60.3 191.2 44 220.8 34.5 243.7c-3.3 7.9-3.3 16.7 0 24.6c14.9 35.7 46.2 87.7 93 131.1C174.5 443.2 239.2 480 320 480c47.8 0 89.9-12.9 126.2-32.5z"/></svg></span></template>
<template id=fa-copy><span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M502.6 70.63l-61.25-61.25C435.4 3.371 427.2 0 418.7 0H255.1c-35.35 0-64 28.66-64 64l.0195 256C192 355.4 220.7 384 256 384h192c35.2 0 64-28.8 64-64V93.25C512 84.77 508.6 76.63 502.6 70.63zM464 320c0 8.836-7.164 16-16 16H255.1c-8.838 0-16-7.164-16-16L239.1 64.13c0-8.836 7.164-16 16-16h128L384 96c0 17.67 14.33 32 32 32h47.1V320zM272 448c0 8.836-7.164 16-16 16H63.1c-8.838 0-16-7.164-16-16L47.98 192.1c0-8.836 7.164-16 16-16H160V128H63.99c-35.35 0-64 28.65-64 64l.0098 256C.002 483.3 28.66 512 64 512h192c35.2 0 64-28.8 64-64v-32h-47.1L272 448z"/></svg></span></template>
<template id=fa-play><span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80V432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41L73 39z"/></svg></span></template>
<template id=fa-clock-rotate-left><span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M75 75L41 41C25.9 25.9 0 36.6 0 57.9V168c0 13.3 10.7 24 24 24H134.1c21.4 0 32.1-25.9 17-41l-30.8-30.8C155 85.5 203 64 256 64c106 0 192 86 192 192s-86 192-192 192c-40.8 0-78.6-12.7-109.7-34.4c-14.5-10.1-34.4-6.6-44.6 7.9s-6.6 34.4 7.9 44.6C151.2 495 201.7 512 256 512c141.4 0 256-114.6 256-256S397.4 0 256 0C185.3 0 121.3 28.7 75 75zm181 53c-13.3 0-24 10.7-24 24V256c0 6.4 2.5 12.5 7 17l72 72c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-65-65V152c0-13.3-10.7-24-24-24z"/></svg></span></template>
<script>
window.playground_copyable = true;
</script>
<script src="elasticlunr-ef4e11c1.min.js"></script>
<script src="mark-09e88c2c.min.js"></script>
<script src="searcher-c2a407aa.js"></script>
<script src="clipboard-1626706a.min.js"></script>
<script src="highlight-abc7f01d.js"></script>
<script src="book-a0b12cfe.js"></script>
<!-- Custom JS scripts -->
<script src="ferris-2317480c.js"></script>
</div>
</body>
</html>