Files
wren/concurrency.html
2021-04-08 04:25:47 +00:00

288 lines
12 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />
<title>Concurrency &ndash; Wren</title>
<script type="application/javascript" src="prism.js" data-manual></script>
<script type="application/javascript" src="codejar.js"></script>
<script type="application/javascript" src="wren.js"></script>
<link rel="stylesheet" type="text/css" href="prism.css" />
<link rel="stylesheet" type="text/css" href="style.css" />
<link href='//fonts.googleapis.com/css?family=Source+Sans+Pro:400,700,400italic,700italic|Source+Code+Pro:400|Lato:400|Sanchez:400italic,400' rel='stylesheet' type='text/css'>
<!-- Tell mobile browsers we're optimized for them and they don't need to crop
the viewport. -->
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
</head>
<body id="top">
<header>
<div class="page">
<div class="main-column">
<h1><a href="./">wren</a></h1>
<h2>a classy little scripting language</h2>
</div>
</div>
</header>
<div class="page">
<nav class="big">
<a href="./"><img src="./wren.svg" class="logo"></a>
<ul>
<li><a href="getting-started.html">Getting Started</a></li>
<li><a href="contributing.html">Contributing</a></li>
<li><a href="blog">Blog</a></li>
<li><a href="try">Try it!</a></li>
</ul>
<section>
<h2>guides</h2>
<ul>
<li><a href="syntax.html">Syntax</a></li>
<li><a href="values.html">Values</a></li>
<li><a href="lists.html">Lists</a></li>
<li><a href="maps.html">Maps</a></li>
<li><a href="method-calls.html">Method Calls</a></li>
<li><a href="control-flow.html">Control Flow</a></li>
<li><a href="variables.html">Variables</a></li>
<li><a href="classes.html">Classes</a></li>
<li><a href="functions.html">Functions</a></li>
<li><a href="concurrency.html">Concurrency</a></li>
<li><a href="error-handling.html">Error Handling</a></li>
<li><a href="modularity.html">Modularity</a></li>
</ul>
</section>
<section>
<h2>API docs</h2>
<ul>
<li><a href="modules">Modules</a></li>
</ul>
</section>
<section>
<h2>reference</h2>
<ul>
<li><a href="cli">Wren CLI</a></li>
<li><a href="embedding">Embedding</a></li>
<li><a href="performance.html">Performance</a></li>
<li><a href="qa.html">Q &amp; A</a></li>
</ul>
</section>
</nav>
<nav class="small">
<table>
<tr>
<div><a href="getting-started.html">Getting Started</a></div>
<div><a href="contributing.html">Contributing</a></div>
<div><a href="blog">Blog</a></div>
<div><a href="try">Try it!</a></div>
</tr>
<tr>
<td colspan="2"><h2>guides</h2></td>
<td><h2>reference</h2></td>
</tr>
<tr>
<td>
<ul>
<li><a href="syntax.html">Syntax</a></li>
<li><a href="values.html">Values</a></li>
<li><a href="lists.html">Lists</a></li>
<li><a href="maps.html">Maps</a></li>
<li><a href="method-calls.html">Method Calls</a></li>
<li><a href="control-flow.html">Control Flow</a></li>
</ul>
</td>
<td>
<ul>
<li><a href="variables.html">Variables</a></li>
<li><a href="classes.html">Classes</a></li>
<li><a href="functions.html">Functions</a></li>
<li><a href="concurrency.html">Concurrency</a></li>
<li><a href="error-handling.html">Error Handling</a></li>
<li><a href="modularity.html">Modularity</a></li>
</ul>
</td>
<td>
<ul>
<li><a href="modules">API/Modules</a></li>
<li><a href="embedding">Embedding</a></li>
<li><a href="performance.html">Performance</a></li>
<li><a href="qa.html">Q &amp; A</a></li>
</ul>
</td>
</tr>
</table>
</nav>
<main>
<h2>Concurrency</h2>
<p>Lightweight concurrency is a key feature of Wren and it is expressed using
<em>fibers</em>. They control how all code is executed, and take the place of
exceptions in <a href="error-handling.html">error handling</a>.</p>
<p>Fibers are a bit like threads except they are <em>cooperatively</em> scheduled. That
means Wren doesn&rsquo;t pause one fiber and switch to another until you tell it to.
You don&rsquo;t have to worry about context switches at random times and all of the
headaches those cause.</p>
<p>Wren takes care of all of the fibers in the VM, so they don&rsquo;t use OS thread
resources, or require heavyweight context switches. Each just needs a bit of
memory for its stack. A fiber will get garbage collected like any other object
when not referenced any more, so you can create them freely.</p>
<p>They are lightweight enough that you can, for example, have a separate fiber for
each entity in a game. Wren can handle thousands of them without breaking a
sweat. For example, when you run Wren in interactive mode, it creates a new
fiber for every line of code you type in.</p>
<h2>Creating fibers <a href="#creating-fibers" name="creating-fibers" class="header-anchor">#</a></h2>
<p>All Wren code runs within the context of a fiber. When you first start a Wren
script, a main fiber is created for you automatically. You can spawn new fibers
using the Fiber class&rsquo;s constructor:</p>
<pre class="snippet">
var fiber = Fiber.new {
System.print("This runs in a separate fiber.")
}
</pre>
<p>It takes a <a href="functions.html">function</a> containing the code the fiber should execute. The
function can take zero or one parameter, but no more than that. Creating the
fiber does not immediately run it. It just wraps the function and sits there,
waiting to be activated.</p>
<h2>Invoking fibers <a href="#invoking-fibers" name="invoking-fibers" class="header-anchor">#</a></h2>
<p>Once you&rsquo;ve created a fiber, you run it by calling its <code>call()</code> method:</p>
<pre class="snippet">
fiber.call()
</pre>
<p>This suspends the current fiber and executes the called one until it reaches the
end of its body or until it passes control to yet another fiber. If it reaches
the end of its body, it is considered <em>done</em>:</p>
<pre class="snippet">
var fiber = Fiber.new {
System.print("It's alive!")
}
System.print(fiber.isDone) //> false
fiber.call() //> It's alive!
System.print(fiber.isDone) //> true
</pre>
<p>When a called fiber finishes, it automatically passes control <em>back</em> to the
fiber that called it. It&rsquo;s a runtime error to try to call a fiber that is
already done.</p>
<h2>Yielding <a href="#yielding" name="yielding" class="header-anchor">#</a></h2>
<p>The main difference between fibers and functions is that a fiber can be
suspended in the middle of its operation and then resumed later. Calling
another fiber is one way to suspend a fiber, but that&rsquo;s more or less the same
as one function calling another.</p>
<p>Things get interesting when a fiber <em>yields</em>. A yielded fiber passes control
<em>back</em> to the fiber that ran it, but <em>remembers where it is</em>. The next time the
fiber is called, it picks up right where it left off and keeps going.</p>
<p>You make a fiber yield by calling the static <code>yield()</code> method on Fiber:</p>
<pre class="snippet">
var fiber = Fiber.new {
System.print("Before yield")
Fiber.yield()
System.print("Resumed")
}
System.print("Before call") //> Before call
fiber.call() //> Before yield
System.print("Calling again") //> Calling again
fiber.call() //> Resumed
System.print("All done") //> All done
</pre>
<p>Note that even though this program uses <em>concurrency</em>, it is still
<em>deterministic</em>. You can reason precisely about what it&rsquo;s doing and aren&rsquo;t at
the mercy of a thread scheduler playing Russian roulette with your code.</p>
<h2>Passing values <a href="#passing-values" name="passing-values" class="header-anchor">#</a></h2>
<p>Calling and yielding fibers is used for passing control, but it can also pass
<em>data</em>. When you call a fiber, you can optionally pass a value to it.</p>
<p>If you create a fiber using a function that takes a parameter, you can pass a
value to it through <code>call()</code>:</p>
<pre class="snippet">
var fiber = Fiber.new {|param|
System.print(param)
}
fiber.call("Here you go") //> Here you go
</pre>
<p>If the fiber has yielded and is waiting to resume, the value you pass to call
becomes the return value of the <code>yield()</code> call when it resumes:</p>
<pre class="snippet">
var fiber = Fiber.new {|param|
System.print(param)
var result = Fiber.yield()
System.print(result)
}
fiber.call("First") //> First
fiber.call("Second") //> Second
</pre>
<p>Fibers can also pass values <em>back</em> when they yield. If you pass an argument to
<code>yield()</code>, that will become the return value of the <code>call()</code> that was used to
invoke the fiber:</p>
<pre class="snippet">
var fiber = Fiber.new {
Fiber.yield("Reply")
}
System.print(fiber.call()) //> Reply
</pre>
<p>This is sort of like how a function call may return a value, except that a fiber
may return a whole sequence of values, one every time it yields.</p>
<h2>Full coroutines <a href="#full-coroutines" name="full-coroutines" class="header-anchor">#</a></h2>
<p>What we&rsquo;ve seen so far is very similar to what you can do with languages like
Python and C# that have <em>generators</em>. Those let you define a function call that
you can suspend and resume. When using the function, it appears like a sequence
you can iterate over.</p>
<p>Wren&rsquo;s fibers can do that, but they can do much more. Like Lua, they are full
<em>coroutines</em>&mdash;they can suspend from anywhere in the callstack. The function
you use to create a fiber can call a method that calls another method that calls
some third method which finally calls yield. When that happens, <em>all</em> of those
method calls &mdash; the entire callstack &mdash; gets suspended. For example:</p>
<pre class="snippet">
var fiber = Fiber.new {
(1..10).each {|i|
Fiber.yield(i)
}
}
</pre>
<p>Here, we&rsquo;re calling <code>yield()</code> from within a <a href="functions.html">function</a> being
passed to the <code>each()</code> method. This works fine in Wren because that inner
<code>yield()</code> call will suspend the call to <code>each()</code> and the function passed to it
as a callback.</p>
<h2>Transferring control <a href="#transferring-control" name="transferring-control" class="header-anchor">#</a></h2>
<p>Fibers have one more trick up their sleeves. When you execute a fiber using
<code>call()</code>, the fiber tracks which fiber it will return to when it yields. This
lets you build up a chain of fiber calls that will eventually unwind back to
the main fiber when all of the called ones yield or finish.</p>
<p>This is usually what you want. But if you&rsquo;re doing something low level, like
writing your own scheduler to manage a pool of fibers, you may not want to treat
them explicitly like a stack.</p>
<p>For rare cases like that, fibers also have a <code>transfer()</code> method. This switches
execution to the transferred fiber and &ldquo;forgets&rdquo; the fiber that was transferred
<em>from</em>. The previous one is suspended, leaving it in whatever state it was in.
You can resume the previous fiber by explicitly transferring back to it, or even
calling it. If you don&rsquo;t, execution stops when the last transferred fiber
returns.</p>
<p>Where <code>call()</code> and <code>yield()</code> are analogous to calling and returning from
functions, <code>transfer()</code> works more like an unstructured goto. It lets you freely
switch control between a number of fibers, all of which act as peers to one
another.</p>
<p><br><hr>
<a class="right" href="error-handling.html">Error Handling &rarr;</a>
<a href="classes.html">&larr; Classes</a></p>
</main>
</div>
<footer>
<div class="page">
<div class="main-column">
<p>Wren lives
<a href="https://github.com/wren-lang/wren">on GitHub</a>
&mdash; Made with &#x2764; by
<a href="http://journal.stuffwithstuff.com/">Bob Nystrom</a> and
<a href="https://github.com/wren-lang/wren/blob/main/AUTHORS">friends</a>.
</p>
<div class="main-column">
</div>
</footer>
</body>
</html>