some minor formatting updates

This commit is contained in:
Carl Fredrik Samson
2020-04-09 00:43:25 +02:00
parent 882ea1bc2a
commit a90ff78349
21 changed files with 1954 additions and 1701 deletions

View File

@@ -1,5 +1,5 @@
<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js">
<html lang="en" class="sidebar-visible no-js light">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
@@ -32,11 +32,11 @@
</head>
<body class="light">
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
var path_to_root = "";
var default_theme = "light";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
@@ -60,8 +60,11 @@
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
document.body.className = theme;
document.querySelector('html').className = theme + ' js';
var html = document.querySelector('html');
html.classList.remove('no-js')
html.classList.remove('light')
html.classList.add(theme);
html.classList.add('js');
</script>
<!-- Hide / unhide sidebar before it is displayed -->
@@ -77,8 +80,8 @@
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="affix"><a href="introduction.html">Introduction</a></li><li><a href="0_background_information.html" class="active"><strong aria-hidden="true">1.</strong> Background information</a></li><li><a href="1_futures_in_rust.html"><strong aria-hidden="true">2.</strong> Futures in Rust</a></li><li><a href="2_waker_context.html"><strong aria-hidden="true">3.</strong> Waker and Context</a></li><li><a href="3_generators_async_await.html"><strong aria-hidden="true">4.</strong> Generators and async/await</a></li><li><a href="4_pin.html"><strong aria-hidden="true">5.</strong> Pin</a></li><li><a href="6_future_example.html"><strong aria-hidden="true">6.</strong> Implementing Futures</a></li><li><a href="8_finished_example.html"><strong aria-hidden="true">7.</strong> Finished example (editable)</a></li><li class="affix"><a href="conclusion.html">Conclusion and exercises</a></li></ol>
<div id="sidebar-scrollbox" class="sidebar-scrollbox">
<ol class="chapter"><li class="expanded affix "><a href="introduction.html">Introduction</a></li><li class="expanded "><a href="0_background_information.html" class="active"><strong aria-hidden="true">1.</strong> Background information</a></li><li class="expanded "><a href="1_futures_in_rust.html"><strong aria-hidden="true">2.</strong> Futures in Rust</a></li><li class="expanded "><a href="2_waker_context.html"><strong aria-hidden="true">3.</strong> Waker and Context</a></li><li class="expanded "><a href="3_generators_async_await.html"><strong aria-hidden="true">4.</strong> Generators and async/await</a></li><li class="expanded "><a href="4_pin.html"><strong aria-hidden="true">5.</strong> Pin</a></li><li class="expanded "><a href="6_future_example.html"><strong aria-hidden="true">6.</strong> Implementing Futures</a></li><li class="expanded "><a href="8_finished_example.html"><strong aria-hidden="true">7.</strong> Finished example (editable)</a></li><li class="expanded affix "><a href="conclusion.html">Conclusion and exercises</a></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
@@ -154,16 +157,16 @@
at the alternatives for handling concurrent programming in general and some
pros and cons for each of them.</p>
<p>While we do that we'll also explain some aspects when it comes to concurrency which
will make it easier for us when we dive in to Futures specifically.</p>
will make it easier for us when we dive into Futures specifically.</p>
<blockquote>
<p>For fun, I've added a small snipped of runnable code with most of the examples.
If you're like me, things get way more interesting then and maybe you'll se some
<p>For fun, I've added a small snippet of runnable code with most of the examples.
If you're like me, things get way more interesting then and maybe you'll see some
things you haven't seen before along the way.</p>
</blockquote>
<h2><a class="header" href="#threads-provided-by-the-operating-system" id="threads-provided-by-the-operating-system">Threads provided by the operating system</a></h2>
<p>Now, one way of accomplishing this is letting the OS take care of everything for
us. We do this by simply spawning a new OS thread for each task we want to
accomplish and write code like we normally would.</p>
<p>Now, one way of accomplishing concurrent programming is letting the OS take care
of everything for us. We do this by simply spawning a new OS thread for each
task we want to accomplish and write code like we normally would.</p>
<p>The runtime we use to handle concurrency for us is the operating system itself.</p>
<p><strong>Advantages:</strong></p>
<ul>
@@ -208,13 +211,13 @@ fn main() {
t2.join().unwrap();
}
</code></pre></pre>
<p>OS threads sure has some pretty big advantages. So why all this talk about
<p>OS threads sure have some pretty big advantages. So why all this talk about
&quot;async&quot; and concurrency in the first place?</p>
<p>First of all. For computers to be <a href="https://en.wikipedia.org/wiki/Efficiency"><em>efficient</em></a> they needs to multitask. Once you
<p>First, for computers to be <a href="https://en.wikipedia.org/wiki/Efficiency"><em>efficient</em></a> they need to multitask. Once you
start to look under the covers (like <a href="https://os.phil-opp.com/async-await/">how an operating system works</a>)
you'll see concurrency everywhere. It's very fundamental in everything we do.</p>
<p>Secondly, we have the web. </p>
<p>Webservers is all about I/O and handling small tasks
<p>Second, we have the web. </p>
<p>Web servers are all about I/O and handling small tasks
(requests). When the number of small tasks is large it's not a good fit for OS
threads as of today because of the memory they require and the overhead involved
when creating new threads. </p>
@@ -226,10 +229,10 @@ right solution. So, just think twice about your problem before you reach for an
async library.</p>
<p>Now, let's look at some other options for multitasking. They all have in common
that they implement a way to do multitasking by having a &quot;userland&quot;
runtime:</p>
runtime.</p>
<h2><a class="header" href="#green-threads" id="green-threads">Green threads</a></h2>
<p>Green threads uses the same mechanism as an OS does by creating a thread for
each task, setting up a stack, save the CPU's state and jump from one
<p>Green threads use the same mechanism as an OS does by creating a thread for
each task, setting up a stack, saving the CPU's state, and jumping from one
task(thread) to another by doing a &quot;context switch&quot;.</p>
<p>We yield control to the scheduler (which is a central part of the runtime in
such a system) which then continues running a different task.</p>
@@ -238,30 +241,30 @@ of execution is stored in each stack so in such a solution there would be no
need for <code>async</code>, <code>await</code>, <code>Futures</code> or <code>Pin</code>. </p>
<p><strong>The typical flow looks like this:</strong></p>
<ol>
<li>Run some non-blocking code</li>
<li>Make a blocking call to some external resource</li>
<li>CPU jumps to the &quot;main&quot; thread which schedules a different thread to run and
&quot;jumps&quot; to that stack</li>
<li>Run some non-blocking code.</li>
<li>Make a blocking call to some external resource.</li>
<li>CPU &quot;jumps&quot; to the &quot;main&quot; thread which schedules a different thread to run and
&quot;jumps&quot; to that stack.</li>
<li>Run some non-blocking code on the new thread until a new blocking call or the
task is finished</li>
<li>&quot;jumps&quot; back to the &quot;main&quot; thread, schedule a new thread which is ready to make
progress and jump to that.</li>
task is finished.</li>
<li>CPU &quot;jumps&quot; back to the &quot;main&quot; thread, schedules a new thread which is ready
to make progress, and &quot;jumps&quot; to that thread.</li>
</ol>
<p>These &quot;jumps&quot; are know as <strong>context switches</strong>. Your OS is doing it many times each
<p>These &quot;jumps&quot; are known as <strong>context switches</strong>. Your OS is doing it many times each
second as you read this.</p>
<p><strong>Advantages:</strong></p>
<ol>
<li>Simple to use. The code will look like it does when using OS threads.</li>
<li>A &quot;context switch&quot; is reasonably fast</li>
<li>Each stack only gets a little memory to start with so you can have hundred of
<li>A &quot;context switch&quot; is reasonably fast.</li>
<li>Each stack only gets a little memory to start with so you can have hundreds of
thousands of green threads running.</li>
<li>It's easy to incorporate <a href="https://cfsamson.gitbook.io/green-threads-explained-in-200-lines-of-rust/green-threads#preemptive-multitasking"><em>preemtion</em></a>
<li>It's easy to incorporate <a href="https://cfsamson.gitbook.io/green-threads-explained-in-200-lines-of-rust/green-threads#preemptive-multitasking"><em>preemption</em></a>
which puts a lot of control in the hands of the runtime implementors.</li>
</ol>
<p><strong>Drawbacks:</strong></p>
<ol>
<li>The stacks might need to grow. Solving this is not easy and will have a cost.</li>
<li>You need to save all the CPU state on every switch</li>
<li>You need to save all the CPU state on every switch.</li>
<li>It's not a <em>zero cost abstraction</em> (Rust had green threads early on and this
was one of the reasons they were removed).</li>
<li>Complicated to implement correctly if you want to support many different
@@ -277,199 +280,199 @@ It's not in any way meant to showcase &quot;best practice&quot;. Just so we're o
the same page.</p>
</blockquote>
<p><em><strong>Press the expand icon in the top right corner to show the example code.</strong></em> </p>
<pre><pre class="playpen"><code class="language-rust edition2018"># #![feature(asm, naked_functions)]
# use std::ptr;
#
# const DEFAULT_STACK_SIZE: usize = 1024 * 1024 * 2;
# const MAX_THREADS: usize = 4;
# static mut RUNTIME: usize = 0;
#
# pub struct Runtime {
# threads: Vec&lt;Thread&gt;,
# current: usize,
# }
#
# #[derive(PartialEq, Eq, Debug)]
# enum State {
# Available,
# Running,
# Ready,
# }
#
# struct Thread {
# id: usize,
# stack: Vec&lt;u8&gt;,
# ctx: ThreadContext,
# state: State,
# task: Option&lt;Box&lt;dyn Fn()&gt;&gt;,
# }
#
# #[derive(Debug, Default)]
# #[repr(C)]
# struct ThreadContext {
# rsp: u64,
# r15: u64,
# r14: u64,
# r13: u64,
# r12: u64,
# rbx: u64,
# rbp: u64,
# thread_ptr: u64,
# }
#
# impl Thread {
# fn new(id: usize) -&gt; Self {
# Thread {
# id,
# stack: vec![0_u8; DEFAULT_STACK_SIZE],
# ctx: ThreadContext::default(),
# state: State::Available,
# task: None,
# }
# }
# }
#
# impl Runtime {
# pub fn new() -&gt; Self {
# let base_thread = Thread {
# id: 0,
# stack: vec![0_u8; DEFAULT_STACK_SIZE],
# ctx: ThreadContext::default(),
# state: State::Running,
# task: None,
# };
#
# let mut threads = vec![base_thread];
# threads[0].ctx.thread_ptr = &amp;threads[0] as *const Thread as u64;
# let mut available_threads: Vec&lt;Thread&gt; = (1..MAX_THREADS).map(|i| Thread::new(i)).collect();
# threads.append(&amp;mut available_threads);
#
# Runtime {
# threads,
# current: 0,
# }
# }
#
# pub fn init(&amp;self) {
# unsafe {
# let r_ptr: *const Runtime = self;
# RUNTIME = r_ptr as usize;
# }
# }
#
# pub fn run(&amp;mut self) -&gt; ! {
# while self.t_yield() {}
# std::process::exit(0);
# }
#
# fn t_return(&amp;mut self) {
# if self.current != 0 {
# self.threads[self.current].state = State::Available;
# self.t_yield();
# }
# }
#
# fn t_yield(&amp;mut self) -&gt; bool {
# let mut pos = self.current;
# while self.threads[pos].state != State::Ready {
# pos += 1;
# if pos == self.threads.len() {
# pos = 0;
# }
# if pos == self.current {
# return false;
# }
# }
#
# if self.threads[self.current].state != State::Available {
# self.threads[self.current].state = State::Ready;
# }
#
# self.threads[pos].state = State::Running;
# let old_pos = self.current;
# self.current = pos;
#
# unsafe {
# switch(&amp;mut self.threads[old_pos].ctx, &amp;self.threads[pos].ctx);
# }
# true
# }
#
# pub fn spawn&lt;F: Fn() + 'static&gt;(f: F){
# unsafe {
# let rt_ptr = RUNTIME as *mut Runtime;
# let available = (*rt_ptr)
# .threads
# .iter_mut()
# .find(|t| t.state == State::Available)
# .expect(&quot;no available thread.&quot;);
#
# let size = available.stack.len();
# let s_ptr = available.stack.as_mut_ptr();
# available.task = Some(Box::new(f));
# available.ctx.thread_ptr = available as *const Thread as u64;
# ptr::write(s_ptr.offset((size - 8) as isize) as *mut u64, guard as u64);
# ptr::write(s_ptr.offset((size - 16) as isize) as *mut u64, call as u64);
# available.ctx.rsp = s_ptr.offset((size - 16) as isize) as u64;
# available.state = State::Ready;
# }
# }
# }
#
# fn call(thread: u64) {
# let thread = unsafe { &amp;*(thread as *const Thread) };
# if let Some(f) = &amp;thread.task {
# f();
# }
# }
#
# #[naked]
# fn guard() {
# unsafe {
# let rt_ptr = RUNTIME as *mut Runtime;
# let rt = &amp;mut *rt_ptr;
# println!(&quot;THREAD {} FINISHED.&quot;, rt.threads[rt.current].id);
# rt.t_return();
# };
# }
#
# pub fn yield_thread() {
# unsafe {
# let rt_ptr = RUNTIME as *mut Runtime;
# (*rt_ptr).t_yield();
# };
# }
#
# #[naked]
# #[inline(never)]
# unsafe fn switch(old: *mut ThreadContext, new: *const ThreadContext) {
# asm!(&quot;
# mov %rsp, 0x00($0)
# mov %r15, 0x08($0)
# mov %r14, 0x10($0)
# mov %r13, 0x18($0)
# mov %r12, 0x20($0)
# mov %rbx, 0x28($0)
# mov %rbp, 0x30($0)
#
# mov 0x00($1), %rsp
# mov 0x08($1), %r15
# mov 0x10($1), %r14
# mov 0x18($1), %r13
# mov 0x20($1), %r12
# mov 0x28($1), %rbx
# mov 0x30($1), %rbp
# mov 0x38($1), %rdi
# ret
# &quot;
# :
# : &quot;r&quot;(old), &quot;r&quot;(new)
# :
# : &quot;alignstack&quot;
# );
# }
# #[cfg(not(windows))]
fn main() {
<pre><pre class="playpen"><code class="language-rust edition2018"><span class="boring">#![feature(asm, naked_functions)]
</span><span class="boring">use std::ptr;
</span><span class="boring">
</span><span class="boring">const DEFAULT_STACK_SIZE: usize = 1024 * 1024 * 2;
</span><span class="boring">const MAX_THREADS: usize = 4;
</span><span class="boring">static mut RUNTIME: usize = 0;
</span><span class="boring">
</span><span class="boring">pub struct Runtime {
</span><span class="boring"> threads: Vec&lt;Thread&gt;,
</span><span class="boring"> current: usize,
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">#[derive(PartialEq, Eq, Debug)]
</span><span class="boring">enum State {
</span><span class="boring"> Available,
</span><span class="boring"> Running,
</span><span class="boring"> Ready,
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">struct Thread {
</span><span class="boring"> id: usize,
</span><span class="boring"> stack: Vec&lt;u8&gt;,
</span><span class="boring"> ctx: ThreadContext,
</span><span class="boring"> state: State,
</span><span class="boring"> task: Option&lt;Box&lt;dyn Fn()&gt;&gt;,
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">#[derive(Debug, Default)]
</span><span class="boring">#[repr(C)]
</span><span class="boring">struct ThreadContext {
</span><span class="boring"> rsp: u64,
</span><span class="boring"> r15: u64,
</span><span class="boring"> r14: u64,
</span><span class="boring"> r13: u64,
</span><span class="boring"> r12: u64,
</span><span class="boring"> rbx: u64,
</span><span class="boring"> rbp: u64,
</span><span class="boring"> thread_ptr: u64,
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">impl Thread {
</span><span class="boring"> fn new(id: usize) -&gt; Self {
</span><span class="boring"> Thread {
</span><span class="boring"> id,
</span><span class="boring"> stack: vec![0_u8; DEFAULT_STACK_SIZE],
</span><span class="boring"> ctx: ThreadContext::default(),
</span><span class="boring"> state: State::Available,
</span><span class="boring"> task: None,
</span><span class="boring"> }
</span><span class="boring"> }
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">impl Runtime {
</span><span class="boring"> pub fn new() -&gt; Self {
</span><span class="boring"> let base_thread = Thread {
</span><span class="boring"> id: 0,
</span><span class="boring"> stack: vec![0_u8; DEFAULT_STACK_SIZE],
</span><span class="boring"> ctx: ThreadContext::default(),
</span><span class="boring"> state: State::Running,
</span><span class="boring"> task: None,
</span><span class="boring"> };
</span><span class="boring">
</span><span class="boring"> let mut threads = vec![base_thread];
</span><span class="boring"> threads[0].ctx.thread_ptr = &amp;threads[0] as *const Thread as u64;
</span><span class="boring"> let mut available_threads: Vec&lt;Thread&gt; = (1..MAX_THREADS).map(|i| Thread::new(i)).collect();
</span><span class="boring"> threads.append(&amp;mut available_threads);
</span><span class="boring">
</span><span class="boring"> Runtime {
</span><span class="boring"> threads,
</span><span class="boring"> current: 0,
</span><span class="boring"> }
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> pub fn init(&amp;self) {
</span><span class="boring"> unsafe {
</span><span class="boring"> let r_ptr: *const Runtime = self;
</span><span class="boring"> RUNTIME = r_ptr as usize;
</span><span class="boring"> }
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> pub fn run(&amp;mut self) -&gt; ! {
</span><span class="boring"> while self.t_yield() {}
</span><span class="boring"> std::process::exit(0);
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> fn t_return(&amp;mut self) {
</span><span class="boring"> if self.current != 0 {
</span><span class="boring"> self.threads[self.current].state = State::Available;
</span><span class="boring"> self.t_yield();
</span><span class="boring"> }
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> fn t_yield(&amp;mut self) -&gt; bool {
</span><span class="boring"> let mut pos = self.current;
</span><span class="boring"> while self.threads[pos].state != State::Ready {
</span><span class="boring"> pos += 1;
</span><span class="boring"> if pos == self.threads.len() {
</span><span class="boring"> pos = 0;
</span><span class="boring"> }
</span><span class="boring"> if pos == self.current {
</span><span class="boring"> return false;
</span><span class="boring"> }
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> if self.threads[self.current].state != State::Available {
</span><span class="boring"> self.threads[self.current].state = State::Ready;
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> self.threads[pos].state = State::Running;
</span><span class="boring"> let old_pos = self.current;
</span><span class="boring"> self.current = pos;
</span><span class="boring">
</span><span class="boring"> unsafe {
</span><span class="boring"> switch(&amp;mut self.threads[old_pos].ctx, &amp;self.threads[pos].ctx);
</span><span class="boring"> }
</span><span class="boring"> true
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> pub fn spawn&lt;F: Fn() + 'static&gt;(f: F){
</span><span class="boring"> unsafe {
</span><span class="boring"> let rt_ptr = RUNTIME as *mut Runtime;
</span><span class="boring"> let available = (*rt_ptr)
</span><span class="boring"> .threads
</span><span class="boring"> .iter_mut()
</span><span class="boring"> .find(|t| t.state == State::Available)
</span><span class="boring"> .expect(&quot;no available thread.&quot;);
</span><span class="boring">
</span><span class="boring"> let size = available.stack.len();
</span><span class="boring"> let s_ptr = available.stack.as_mut_ptr();
</span><span class="boring"> available.task = Some(Box::new(f));
</span><span class="boring"> available.ctx.thread_ptr = available as *const Thread as u64;
</span><span class="boring"> ptr::write(s_ptr.offset((size - 8) as isize) as *mut u64, guard as u64);
</span><span class="boring"> ptr::write(s_ptr.offset((size - 16) as isize) as *mut u64, call as u64);
</span><span class="boring"> available.ctx.rsp = s_ptr.offset((size - 16) as isize) as u64;
</span><span class="boring"> available.state = State::Ready;
</span><span class="boring"> }
</span><span class="boring"> }
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">fn call(thread: u64) {
</span><span class="boring"> let thread = unsafe { &amp;*(thread as *const Thread) };
</span><span class="boring"> if let Some(f) = &amp;thread.task {
</span><span class="boring"> f();
</span><span class="boring"> }
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">#[naked]
</span><span class="boring">fn guard() {
</span><span class="boring"> unsafe {
</span><span class="boring"> let rt_ptr = RUNTIME as *mut Runtime;
</span><span class="boring"> let rt = &amp;mut *rt_ptr;
</span><span class="boring"> println!(&quot;THREAD {} FINISHED.&quot;, rt.threads[rt.current].id);
</span><span class="boring"> rt.t_return();
</span><span class="boring"> };
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">pub fn yield_thread() {
</span><span class="boring"> unsafe {
</span><span class="boring"> let rt_ptr = RUNTIME as *mut Runtime;
</span><span class="boring"> (*rt_ptr).t_yield();
</span><span class="boring"> };
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">#[naked]
</span><span class="boring">#[inline(never)]
</span><span class="boring">unsafe fn switch(old: *mut ThreadContext, new: *const ThreadContext) {
</span><span class="boring"> asm!(&quot;
</span><span class="boring"> mov %rsp, 0x00($0)
</span><span class="boring"> mov %r15, 0x08($0)
</span><span class="boring"> mov %r14, 0x10($0)
</span><span class="boring"> mov %r13, 0x18($0)
</span><span class="boring"> mov %r12, 0x20($0)
</span><span class="boring"> mov %rbx, 0x28($0)
</span><span class="boring"> mov %rbp, 0x30($0)
</span><span class="boring">
</span><span class="boring"> mov 0x00($1), %rsp
</span><span class="boring"> mov 0x08($1), %r15
</span><span class="boring"> mov 0x10($1), %r14
</span><span class="boring"> mov 0x18($1), %r13
</span><span class="boring"> mov 0x20($1), %r12
</span><span class="boring"> mov 0x28($1), %rbx
</span><span class="boring"> mov 0x30($1), %rbp
</span><span class="boring"> mov 0x38($1), %rdi
</span><span class="boring"> ret
</span><span class="boring"> &quot;
</span><span class="boring"> :
</span><span class="boring"> : &quot;r&quot;(old), &quot;r&quot;(new)
</span><span class="boring"> :
</span><span class="boring"> : &quot;alignstack&quot;
</span><span class="boring"> );
</span><span class="boring">}
</span><span class="boring">#[cfg(not(windows))]
</span>fn main() {
let mut runtime = Runtime::new();
runtime.init();
Runtime::spawn(|| {
@@ -485,22 +488,22 @@ fn main() {
});
runtime.run();
}
# #[cfg(windows)]
# fn main() { }
</code></pre></pre>
<span class="boring">#[cfg(windows)]
</span><span class="boring">fn main() { }
</span></code></pre></pre>
<p>Still hanging in there? Good. Don't get frustrated if the code above is
difficult to understand. If I hadn't written it myself I would probably feel
the same. You can always go back and read the book which explains it later.</p>
<h2><a class="header" href="#callback-based-approaches" id="callback-based-approaches">Callback based approaches</a></h2>
<p>You probably already know what we're going to talk about in the next paragraphs
from Javascript which I assume most know. </p>
from JavaScript which I assume most know. </p>
<blockquote>
<p>If your exposure to Javascript callbacks has given you any sorts of PTSD earlier
<p>If your exposure to JavaScript callbacks has given you any sorts of PTSD earlier
in life, close your eyes now and scroll down for 2-3 seconds. You'll find a link
there that takes you to safety.</p>
</blockquote>
<p>The whole idea behind a callback based approach is to save a pointer to a set of
instructions we want to run later together with whatever state is needed. In rust this
instructions we want to run later together with whatever state is needed. In Rust this
would be a <code>closure</code>. In the example below, we save this information in a <code>HashMap</code>
but it's not the only option.</p>
<p>The basic idea of <em>not</em> involving threads as a primary way to achieve concurrency
@@ -514,13 +517,13 @@ Rust uses today which we'll soon get to.</p>
</ul>
<p><strong>Drawbacks:</strong></p>
<ul>
<li>Each task must save the state it needs for later, the memory usage will grow
<li>Since each task must save the state it needs for later, the memory usage will grow
linearly with the number of callbacks in a chain of computations.</li>
<li>Can be hard to reason about, many people already know this as as &quot;callback hell&quot;.</li>
<li>Can be hard to reason about. Many people already know this as &quot;callback hell&quot;.</li>
<li>It's a very different way of writing a program, and will require a substantial
rewrite to go from a &quot;normal&quot; program flow to one that uses a &quot;callback based&quot; flow.</li>
<li>Sharing state between tasks is a hard problem in Rust using this approach due
to it's ownership model.</li>
to its ownership model.</li>
</ul>
<p>An extremely simplified example of a how a callback based approach could look
like is:</p>
@@ -593,7 +596,7 @@ impl Runtime {
}
</code></pre></pre>
<p>We're keeping this super simple, and you might wonder what's the difference
between this approach and the one using OS threads an passing in the callbacks
between this approach and the one using OS threads and passing in the callbacks
to the OS threads directly.</p>
<p>The difference is that the callbacks are run on the
same thread using this example. The OS threads we create are basically just used
@@ -602,12 +605,12 @@ as timers but could represent any kind of resource that we'll have to wait for.<
<p>You might start to wonder by now, when are we going to talk about Futures?</p>
<p>Well, we're getting there. You see <code>promises</code>, <code>futures</code> and other names for
deferred computations are often used interchangeably. </p>
<p>There are formal differences between them but we'll not cover that here but it's
worth explaining <code>promises</code> a bit since they're widely known due to being used
in Javascript and have a lot in common with Rusts Futures.</p>
<p>First of all, many languages has a concept of promises but I'll use the ones
from Javascript in the examples below.</p>
<p>Promises is one way to deal with the complexity which comes with a callback
<p>There are formal differences between them, but we won't cover those
here. It's worth explaining <code>promises</code> a bit since they're widely known due to
their use in JavaScript. Promises also have a lot in common with Rust's Futures.</p>
<p>First of all, many languages have a concept of promises, but I'll use the one
from JavaScript in the examples below.</p>
<p>Promises are one way to deal with the complexity which comes with a callback
based approach.</p>
<p>Instead of:</p>
<pre><code class="language-js ignore">setTimer(200, () =&gt; {
@@ -618,21 +621,21 @@ based approach.</p>
});
});
</code></pre>
<p>We can to this:</p>
<p>We can do this:</p>
<pre><code class="language-js ignore">function timer(ms) {
return new Promise((resolve) =&gt; setTimeout(resolve, ms))
return new Promise((resolve) =&gt; setTimeout(resolve, ms));
}
timer(200)
.then(() =&gt; return timer(100))
.then(() =&gt; return timer(50))
.then(() =&gt; console.log(&quot;I'm the last one));
.then(() =&gt; console.log(&quot;I'm the last one&quot;));
</code></pre>
<p>The change is even more substantial under the hood. You see, promises return
a state machine which can be in one of three states: <code>pending</code>, <code>fulfilled</code> or
<code>rejected</code>. </p>
<p>When we call <code>timer(200)</code> in the sample above, we get back a promise in the state <code>pending</code>.</p>
<p>Since promises are re-written as state machines they also enable an even better
<p>Since promises are re-written as state machines, they also enable an even better
syntax which allows us to write our last example like this:</p>
<pre><code class="language-js ignore">async function run() {
await timer(200);
@@ -641,20 +644,20 @@ syntax which allows us to write our last example like this:</p>
console.log(&quot;I'm the last one&quot;);
}
</code></pre>
<p>You can consider the <code>run</code> function a <em>pausable</em> task consisting of several
<p>You can consider the <code>run</code> function as a <em>pausable</em> task consisting of several
sub-tasks. On each &quot;await&quot; point it yields control to the scheduler (in this
case it's the well known Javascript event loop). </p>
<p>Once one of the sub-tasks changes state to either <code>fulfilled</code> or <code>rejected</code> the
case it's the well-known JavaScript event loop). </p>
<p>Once one of the sub-tasks changes state to either <code>fulfilled</code> or <code>rejected</code>, the
task is scheduled to continue to the next step.</p>
<p>Syntactically, Rusts Futures 1.0 was a lot like the promises example above and
Rusts Futures 3.0 is a lot like async/await in our last example.</p>
<p>Now this is also where the similarities with Rusts Futures stop. The reason we
go through all this is to get an introduction and get into the right mindset for
exploring Rusts Futures.</p>
<p>Syntactically, Rust's Futures 0.1 was a lot like the promises example above, and
Rust's Futures 0.3 is a lot like async/await in our last example.</p>
<p>Now this is also where the similarities between JavaScript promises and Rust's
Futures stop. The reason we go through all this is to get an introduction and
get into the right mindset for exploring Rust's Futures.</p>
<blockquote>
<p>To avoid confusion later on: There's one difference you should know. Javascript
<p>To avoid confusion later on: There's one difference you should know. JavaScript
promises are <em>eagerly</em> evaluated. That means that once it's created, it starts
running a task. Rusts Futures on the other hand is <em>lazily</em> evaluated. They
running a task. Rust's Futures on the other hand are <em>lazily</em> evaluated. They
need to be polled once before they do any work.</p>
</blockquote>
<br />
@@ -735,6 +738,18 @@ need to be polled once before they do any work.</p>
<script type="text/javascript">
window.playpen_line_numbers = true;
</script>
<script type="text/javascript">
window.playpen_copyable = true;
</script>
<script src="ace.js" type="text/javascript" charset="utf-8"></script>
<script src="editor.js" type="text/javascript" charset="utf-8"></script>
<script src="mode-rust.js" type="text/javascript" charset="utf-8"></script>