finished background information

This commit is contained in:
Carl Fredrik Samson
2020-04-04 23:40:48 +02:00
parent c8cff655ec
commit 0d0c265dc7
16 changed files with 399 additions and 268 deletions

View File

@@ -80,7 +80,7 @@
<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="1_why_futures.html"><strong aria-hidden="true">1.</strong> Why Futures</a></li><li><a href="1_background_information.html"><strong aria-hidden="true">2.</strong> Some background information</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_pin.html"><strong aria-hidden="true">4.</strong> Generators</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> Futures - our main example</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>
<ol class="chapter"><li class="affix"><a href="introduction.html">Introduction</a></li><li><a href="0_background_information.html"><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_pin.html"><strong aria-hidden="true">4.</strong> Generators</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> Futures - our main example</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>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
@@ -191,14 +191,22 @@ explore further and try your own ideas.</p>
<code>async_std</code>, <code>Futures</code>, <code>libc</code>, <code>crossbeam</code> and many other libraries which so
much is built upon. Even the RFCs that much of the design is built upon is
very well written and very helpful. So thanks!</p>
<h1><a class="header" href="#why-futures" id="why-futures">Why Futures</a></h1>
<h1><a class="header" href="#some-background-information" id="some-background-information">Some Background Information</a></h1>
<p>Before we go into the details about Futures in Rust, let's take a quick look
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 get some information on concurrency which will make it
easier for us when we dive in to 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
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
<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>The runtime we use to handle concurrency for us is the operating system itself.</p>
<p><strong>Advantages:</strong></p>
<ul>
<li>Simple</li>
@@ -210,14 +218,14 @@ accomplish and write code like we normally would.</p>
<ul>
<li>OS level threads come with a rather large stack. If you have many tasks
waiting simultaneously (like you would in a web-server under heavy load) you'll
run out of memory pretty soon.</li>
run out of memory pretty fast.</li>
<li>There are a lot of syscalls involved. This can be pretty costly when the number
of tasks is high.</li>
<li>The OS has many things it needs to handle. It might not switch back to your
thread as fast as you'd wish.</li>
<li>Might not be an option on some systems</li>
</ul>
<p>Using OS threads in Rust looks like this:</p>
<p><strong>Using OS threads in Rust looks like this:</strong></p>
<pre><pre class="playpen"><code class="language-rust">use std::thread;
fn main() {
@@ -245,18 +253,20 @@ fn main() {
<p>OS threads sure has 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> it needs 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>)
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. Webservers is 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. That's why you'll see so many async web frameworks
and database drivers today.</p>
<p>However, for a huge number of tasks, the standard OS threads will often be the
when creating new threads. This gets even more relevant when the load is variable
which means the current number of tasks a program has at any point in time is
unpredictable. That's why you'll see so many async web frameworks and database
drivers today.</p>
<p>However, for a huge number of problems, the standard OS threads will often be the
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 implementing a &quot;userland&quot;
that they implement a way to do multitasking by having a &quot;userland&quot;
runtime:</p>
<h2><a class="header" href="#green-threads" id="green-threads">Green threads</a></h2>
<p>Green threads has been popularized by GO in the recent years. Green threads
@@ -297,10 +307,12 @@ platforms.</li>
<p>If you were to implement green threads in Rust, it could look something like
this:</p>
<blockquote>
<p>The example presented below is from an earlier book I wrote about green
threads called <a href="https://cfsamson.gitbook.io/green-threads-explained-in-200-lines-of-rust/">Green Threads Explained in 200 lines of Rust.</a>
<p>The example presented below is an adapted example from an earlier gitbook I
wrote about green threads called <a href="https://cfsamson.gitbook.io/green-threads-explained-in-200-lines-of-rust/">Green Threads Explained in 200 lines of Rust.</a>
If you want to know what's going on you'll find everything explained in detail
in that book.</p>
in that book. The code below is wildly unsafe and it's just to show a real example.
It's not in any way meant to showcase &quot;best practice&quot;. Just so we're on
the same page.</p>
</blockquote>
<pre><pre class="playpen"><code class="language-rust">#![feature(asm)]
#![feature(naked_functions)]
@@ -327,6 +339,7 @@ struct Thread {
stack: Vec&lt;u8&gt;,
ctx: ThreadContext,
state: State,
task: Option&lt;Box&lt;dyn Fn()&gt;&gt;,
}
#[derive(Debug, Default)]
@@ -339,6 +352,7 @@ struct ThreadContext {
r12: u64,
rbx: u64,
rbp: u64,
thread_ptr: u64,
}
impl Thread {
@@ -348,6 +362,7 @@ impl Thread {
stack: vec![0_u8; DEFAULT_STACK_SIZE],
ctx: ThreadContext::default(),
state: State::Available,
task: None,
}
}
}
@@ -359,11 +374,14 @@ impl Runtime {
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,
@@ -400,40 +418,56 @@ impl Runtime {
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);
}
self.threads.len() &gt; 0
true
}
pub fn spawn(&amp;mut self, f: fn()) {
let available = self
.threads
.iter_mut()
.find(|t| t.state == State::Available)
.expect(&quot;no available thread.&quot;);
let size = available.stack.len();
pub fn spawn&lt;F: Fn() + 'static&gt;(f: F){
unsafe {
let s_ptr = available.stack.as_mut_ptr().offset(size as isize);
let s_ptr = (s_ptr as usize &amp; !15) as *mut u8;
ptr::write(s_ptr.offset(-24) as *mut u64, guard as u64);
ptr::write(s_ptr.offset(-32) as *mut u64, f as u64);
available.ctx.rsp = s_ptr.offset(-32) as u64;
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;
}
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;
(*rt_ptr).t_return();
let rt = &amp;mut *rt_ptr;
println!(&quot;THREAD {} FINISHED.&quot;, rt.threads[rt.current].id);
rt.t_return();
};
}
@@ -455,7 +489,7 @@ unsafe fn switch(old: *mut ThreadContext, new: *const ThreadContext) {
mov %r12, 0x20($0)
mov %rbx, 0x28($0)
mov %rbp, 0x30($0)
mov 0x00($1), %rsp
mov 0x08($1), %r15
mov 0x10($1), %r14
@@ -463,43 +497,45 @@ unsafe fn switch(old: *mut ThreadContext, new: *const ThreadContext) {
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;r&quot;(old), &quot;r&quot;(new)
:
: &quot;volatile&quot;, &quot;alignstack&quot;
: &quot;alignstack&quot;
);
}
fn main() {
let mut runtime = Runtime::new();
runtime.init();
runtime.spawn(|| {
println!(&quot;THREAD 1 STARTING&quot;);
let id = 1;
for i in 0..10 {
println!(&quot;thread: {} counter: {}&quot;, id, i);
yield_thread();
}
println!(&quot;THREAD 1 FINISHED&quot;);
Runtime::spawn(|| {
println!(&quot;I haven't implemented a timer in this example.&quot;);
yield_thread();
println!(&quot;Finally, notice how the tasks are executed concurrently.&quot;);
});
runtime.spawn(|| {
println!(&quot;THREAD 2 STARTING&quot;);
let id = 2;
for i in 0..15 {
println!(&quot;thread: {} counter: {}&quot;, id, i);
yield_thread();
}
println!(&quot;THREAD 2 FINISHED&quot;);
Runtime::spawn(|| {
println!(&quot;But we can still nest tasks...&quot;);
Runtime::spawn(|| {
println!(&quot;...like this!&quot;);
})
});
runtime.run();
}
</code></pre></pre>
<h3><a class="header" href="#callback-based-approach" id="callback-based-approach">Callback based approach</a></h3>
<p>You probably already know this from Javascript since it's extremely common.
The whole idea behind a callback based approach is to save a pointer to a
set of instructions we want to run later on.</p>
<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>
<h3><a class="header" href="#callback-based-approaches" id="callback-based-approaches">Callback based approaches</a></h3>
<p>You probably already know what we're going to talk about in the next paragraphs
from Javascript which I assume most know. If your exposure to Javascript 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>
<p>The whole idea behind a callback based approach is to save a pointer to a set of
instructions we want to run later. We can save that pointer on the stack before
we yield control to the runtime, or in some sort of collection as we do below.</p>
<p>The basic idea of not involving threads as a primary way to achieve concurrency
is the common denominator for the rest of the approaches. Including the one
Rust uses today which we'll soon get to.</p>
@@ -512,8 +548,10 @@ Rust uses today which we'll soon get to.</p>
<p><strong>Drawbacks:</strong></p>
<ul>
<li>Each task must save the state it needs for later, the memory usage will grow
linearly with the number of callbacks in a task.</li>
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>It's a very different way of writing a program, and it can be difficult to
get an understanding of the program flow.</li>
<li>Sharing state between tasks is a hard problem in Rust using this approach due
to it's ownership model.</li>
</ul>
@@ -594,22 +632,23 @@ same thread using this example. The OS threads we create are basically just used
as timers.</p>
<h2><a class="header" href="#from-callbacks-to-promises" id="from-callbacks-to-promises">From callbacks to promises</a></h2>
<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 <code>deferreds</code> are
often used interchangeably in day to day jargon. There are some formal
differences between which is used which we'll not cover here but it's worth
explaining promises a bit as a segway to Rusts 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. 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 beeing used in
Javascript and will serve as segway to Rusts Futures.</p>
<p>First of all, many languages has a concept of promises but I'll use the ones
from Javascript as an example.</p>
from Javascript in the examples below.</p>
<p>Promises is 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; {
setTimer(100, () =&gt; {
setTimer(50, () =&gt; {
console.log(&quot;I'm the last one&quot;);
})
})
})
setTimer(100, () =&gt; {
setTimer(50, () =&gt; {
console.log(&quot;I'm the last one&quot;);
});
});
});
</code></pre>
<p>We can to this:</p>
<pre><code class="language-js ignore">function timer(ms) {
@@ -622,12 +661,11 @@ timer(200)
.then(() =&gt; console.log(&quot;I'm the last one));
</code></pre>
<p>The change is even more substantial under the hood. You see, promises return
a state which is either <code>pending</code>, <code>fulfilled</code> or <code>rejected</code>. So when we call
<code>timer(200)</code> in the sample above, we get back a promise in the state <code>pending</code>.</p>
<p>A <code>promise</code> is a state machine which makes one <code>step</code> when the I/O operation
is finished.</p>
<p>This allows for an even better syntax where we now can write our last example
like this:</p>
a state machine which can be in one of three states: <code>pending</code>, <code>fulfilled</code> or
<code>rejected</code>. So 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
syntax where we now can write our last example like this:</p>
<pre><code>async function run() {
await timer(200);
await timer(100);
@@ -635,19 +673,23 @@ like this:</p>
console.log(&quot;I'm the last one&quot;);
}
</code></pre>
<p>Now this is also where the similarities stop. The reason we went through all
this is to get an introduction and get into the right mindset for exploring
Rusts Futures.</p>
<p>Syntactically though, this is relevant. 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>You can consider the <code>run</code> function 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). Once one of the sub-tasks changes
state to either <code>fulfilled</code> or <code>rejected</code> the task is sheduled 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>
<blockquote>
<p>To avoid confusion later on: There is 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
need to be polled once before they do any work. You'll see in a moment.</p>
</blockquote>
<h1><a class="header" href="#some-background-information" id="some-background-information">Some background information</a></h1>
<h1><a class="header" href="#futures-in-rust" id="futures-in-rust">Futures in Rust</a></h1>
<blockquote>
<p><strong>Relevant for:</strong></p>
<ul>
@@ -1724,7 +1766,7 @@ extra care must be taken when implementing <code>Drop</code> for pinned types.</
<h2><a class="header" href="#putting-it-all-together" id="putting-it-all-together">Putting it all together</a></h2>
<p>This is exactly what we'll do when we implement our own <code>Futures</code> stay tuned,
we're soon finished.</p>
<h1><a class="header" href="#futures-in-rust" id="futures-in-rust">Futures in Rust</a></h1>
<h1><a class="header" href="#futures-in-rust-1" id="futures-in-rust-1">Futures in Rust</a></h1>
<p>We'll create our own <code>Futures</code> together with a fake reactor and a simple
executor which allows you to edit, run an play around with the code right here
in your browser.</p>