about summary refs log tree commit diff
path: root/doc/guide-tasks.md
diff options
context:
space:
mode:
authorAlex Crichton <alex@alexcrichton.com>2014-01-28 12:01:57 -0800
committerAlex Crichton <alex@alexcrichton.com>2014-02-02 10:59:14 -0800
commit864b434bfa3fd5b3ea9e38958652ed1abdc24f1d (patch)
tree55d1693b52303c3ae620762f31b616663746a442 /doc/guide-tasks.md
parent2ff16b184950f5b24c3b2a4bf57b6dd7b3fbbe17 (diff)
downloadrust-864b434bfa3fd5b3ea9e38958652ed1abdc24f1d.tar.gz
rust-864b434bfa3fd5b3ea9e38958652ed1abdc24f1d.zip
Move doc/ to src/doc/
We generate documentation into the doc/ directory, so we shouldn't be
intermingling source files with generated files
Diffstat (limited to 'doc/guide-tasks.md')
-rw-r--r--doc/guide-tasks.md519
1 files changed, 0 insertions, 519 deletions
diff --git a/doc/guide-tasks.md b/doc/guide-tasks.md
deleted file mode 100644
index c3bdbe3a3ee..00000000000
--- a/doc/guide-tasks.md
+++ /dev/null
@@ -1,519 +0,0 @@
-% The Rust Tasks and Communication Guide
-
-# Introduction
-
-Rust provides safe concurrency through a combination
-of lightweight, memory-isolated tasks and message passing.
-This guide will describe the concurrency model in Rust, how it
-relates to the Rust type system, and introduce
-the fundamental library abstractions for constructing concurrent programs.
-
-Rust tasks are not the same as traditional threads: rather,
-they are considered _green threads_, lightweight units of execution that the Rust
-runtime schedules cooperatively onto a small number of operating system threads.
-On a multi-core system Rust tasks will be scheduled in parallel by default.
-Because tasks are significantly
-cheaper to create than traditional threads, Rust can create hundreds of
-thousands of concurrent tasks on a typical 32-bit system.
-In general, all Rust code executes inside a task, including the `main` function.
-
-In order to make efficient use of memory Rust tasks have dynamically sized stacks.
-A task begins its life with a small
-amount of stack space (currently in the low thousands of bytes, depending on
-platform), and acquires more stack as needed.
-Unlike in languages such as C, a Rust task cannot accidentally write to
-memory beyond the end of the stack, causing crashes or worse.
-
-Tasks provide failure isolation and recovery. When a fatal error occurs in Rust
-code as a result of an explicit call to `fail!()`, an assertion failure, or
-another invalid operation, the runtime system destroys the entire
-task. Unlike in languages such as Java and C++, there is no way to `catch` an
-exception. Instead, tasks may monitor each other for failure.
-
-Tasks use Rust's type system to provide strong memory safety guarantees. In
-particular, the type system guarantees that tasks cannot share mutable state
-with each other. Tasks communicate with each other by transferring _owned_
-data through the global _exchange heap_.
-
-## A note about the libraries
-
-While Rust's type system provides the building blocks needed for safe
-and efficient tasks, all of the task functionality itself is implemented
-in the standard and extra libraries, which are still under development
-and do not always present a consistent or complete interface.
-
-For your reference, these are the standard modules involved in Rust
-concurrency at this writing:
-
-* [`std::task`] - All code relating to tasks and task scheduling,
-* [`std::comm`] - The message passing interface,
-* [`extra::comm`] - Additional messaging types based on `std::comm`,
-* [`extra::sync`] - More exotic synchronization tools, including locks,
-* [`extra::arc`] - The Arc (atomically reference counted) type,
-  for safely sharing immutable data,
-* [`extra::future`] - A type representing values that may be computed concurrently and retrieved at a later time.
-
-[`std::task`]: std/task/index.html
-[`std::comm`]: std/comm/index.html
-[`extra::comm`]: extra/comm/index.html
-[`extra::sync`]: extra/sync/index.html
-[`extra::arc`]: extra/arc/index.html
-[`extra::future`]: extra/future/index.html
-
-# Basics
-
-The programming interface for creating and managing tasks lives
-in the `task` module of the `std` library, and is thus available to all
-Rust code by default. At its simplest, creating a task is a matter of
-calling the `spawn` function with a closure argument. `spawn` executes the
-closure in the new task.
-
-~~~~
-# use std::task::spawn;
-
-// Print something profound in a different task using a named function
-fn print_message() { println!("I am running in a different task!"); }
-spawn(print_message);
-
-// Print something more profound in a different task using a lambda expression
-spawn(proc() println!("I am also running in a different task!") );
-~~~~
-
-In Rust, there is nothing special about creating tasks: a task is not a
-concept that appears in the language semantics. Instead, Rust's type system
-provides all the tools necessary to implement safe concurrency: particularly,
-_owned types_. The language leaves the implementation details to the standard
-library.
-
-The `spawn` function has a very simple type signature: `fn spawn(f:
-proc())`. Because it accepts only owned closures, and owned closures
-contain only owned data, `spawn` can safely move the entire closure
-and all its associated state into an entirely different task for
-execution. Like any closure, the function passed to `spawn` may capture
-an environment that it carries across tasks.
-
-~~~
-# use std::task::spawn;
-# fn generate_task_number() -> int { 0 }
-// Generate some state locally
-let child_task_number = generate_task_number();
-
-spawn(proc() {
-    // Capture it in the remote task
-    println!("I am child number {}", child_task_number);
-});
-~~~
-
-## Communication
-
-Now that we have spawned a new task, it would be nice if we could
-communicate with it. Recall that Rust does not have shared mutable
-state, so one task may not manipulate variables owned by another task.
-Instead we use *pipes*.
-
-A pipe is simply a pair of endpoints: one for sending messages and another for
-receiving messages. Pipes are low-level communication building-blocks and so
-come in a variety of forms, each one appropriate for a different use case. In
-what follows, we cover the most commonly used varieties.
-
-The simplest way to create a pipe is to use `Chan::new`
-function to create a `(Port, Chan)` pair. In Rust parlance, a *channel*
-is a sending endpoint of a pipe, and a *port* is the receiving
-endpoint. Consider the following example of calculating two results
-concurrently:
-
-~~~~
-# use std::task::spawn;
-
-let (port, chan): (Port<int>, Chan<int>) = Chan::new();
-
-spawn(proc() {
-    let result = some_expensive_computation();
-    chan.send(result);
-});
-
-some_other_expensive_computation();
-let result = port.recv();
-# fn some_expensive_computation() -> int { 42 }
-# fn some_other_expensive_computation() {}
-~~~~
-
-Let's examine this example in detail. First, the `let` statement creates a
-stream for sending and receiving integers (the left-hand side of the `let`,
-`(chan, port)`, is an example of a *destructuring let*: the pattern separates
-a tuple into its component parts).
-
-~~~~
-let (port, chan): (Port<int>, Chan<int>) = Chan::new();
-~~~~
-
-The child task will use the channel to send data to the parent task,
-which will wait to receive the data on the port. The next statement
-spawns the child task.
-
-~~~~
-# use std::task::spawn;
-# fn some_expensive_computation() -> int { 42 }
-# let (port, chan) = Chan::new();
-spawn(proc() {
-    let result = some_expensive_computation();
-    chan.send(result);
-});
-~~~~
-
-Notice that the creation of the task closure transfers `chan` to the child
-task implicitly: the closure captures `chan` in its environment. Both `Chan`
-and `Port` are sendable types and may be captured into tasks or otherwise
-transferred between them. In the example, the child task runs an expensive
-computation, then sends the result over the captured channel.
-
-Finally, the parent continues with some other expensive
-computation, then waits for the child's result to arrive on the
-port:
-
-~~~~
-# fn some_other_expensive_computation() {}
-# let (port, chan) = Chan::<int>::new();
-# chan.send(0);
-some_other_expensive_computation();
-let result = port.recv();
-~~~~
-
-The `Port` and `Chan` pair created by `Chan::new` enables efficient
-communication between a single sender and a single receiver, but multiple
-senders cannot use a single `Chan`, and multiple receivers cannot use a single
-`Port`.  What if our example needed to compute multiple results across a number
-of tasks? The following program is ill-typed:
-
-~~~ {.ignore}
-# use std::task::{spawn};
-# fn some_expensive_computation() -> int { 42 }
-let (port, chan) = Chan::new();
-
-spawn(proc() {
-    chan.send(some_expensive_computation());
-});
-
-// ERROR! The previous spawn statement already owns the channel,
-// so the compiler will not allow it to be captured again
-spawn(proc() {
-    chan.send(some_expensive_computation());
-});
-~~~
-
-Instead we can use a `SharedChan`, a type that allows a single
-`Chan` to be shared by multiple senders.
-
-~~~
-# use std::task::spawn;
-
-let (port, chan) = SharedChan::new();
-
-for init_val in range(0u, 3) {
-    // Create a new channel handle to distribute to the child task
-    let child_chan = chan.clone();
-    spawn(proc() {
-        child_chan.send(some_expensive_computation(init_val));
-    });
-}
-
-let result = port.recv() + port.recv() + port.recv();
-# fn some_expensive_computation(_i: uint) -> int { 42 }
-~~~
-
-Here we transfer ownership of the channel into a new `SharedChan` value.  Like
-`Chan`, `SharedChan` is a non-copyable, owned type (sometimes also referred to
-as an *affine* or *linear* type). Unlike with `Chan`, though, the programmer
-may duplicate a `SharedChan`, with the `clone()` method.  A cloned
-`SharedChan` produces a new handle to the same channel, allowing multiple
-tasks to send data to a single port.  Between `spawn`, `Chan` and
-`SharedChan`, we have enough tools to implement many useful concurrency
-patterns.
-
-Note that the above `SharedChan` example is somewhat contrived since
-you could also simply use three `Chan` pairs, but it serves to
-illustrate the point. For reference, written with multiple streams, it
-might look like the example below.
-
-~~~
-# use std::task::spawn;
-# use std::vec;
-
-// Create a vector of ports, one for each child task
-let ports = vec::from_fn(3, |init_val| {
-    let (port, chan) = Chan::new();
-    spawn(proc() {
-        chan.send(some_expensive_computation(init_val));
-    });
-    port
-});
-
-// Wait on each port, accumulating the results
-let result = ports.iter().fold(0, |accum, port| accum + port.recv() );
-# fn some_expensive_computation(_i: uint) -> int { 42 }
-~~~
-
-## Backgrounding computations: Futures
-With `extra::future`, rust has a mechanism for requesting a computation and getting the result
-later.
-
-The basic example below illustrates this.
-
-~~~
-# fn make_a_sandwich() {};
-fn fib(n: u64) -> u64 {
-    // lengthy computation returning an uint
-    12586269025
-}
-
-let mut delayed_fib = extra::future::Future::spawn(proc() fib(50));
-make_a_sandwich();
-println!("fib(50) = {:?}", delayed_fib.get())
-~~~
-
-The call to `future::spawn` returns immediately a `future` object regardless of how long it
-takes to run `fib(50)`. You can then make yourself a sandwich while the computation of `fib` is
-running. The result of the execution of the method is obtained by calling `get` on the future.
-This call will block until the value is available (*i.e.* the computation is complete). Note that
-the future needs to be mutable so that it can save the result for next time `get` is called.
-
-Here is another example showing how futures allow you to background computations. The workload will
-be distributed on the available cores.
-
-~~~
-# use std::vec;
-fn partial_sum(start: uint) -> f64 {
-    let mut local_sum = 0f64;
-    for num in range(start*100000, (start+1)*100000) {
-        local_sum += (num as f64 + 1.0).powf(&-2.0);
-    }
-    local_sum
-}
-
-fn main() {
-    let mut futures = vec::from_fn(1000, |ind| extra::future::Future::spawn( proc() { partial_sum(ind) }));
-
-    let mut final_res = 0f64;
-    for ft in futures.mut_iter()  {
-        final_res += ft.get();
-    }
-    println!("π^2/6 is not far from : {}", final_res);
-}
-~~~
-
-## Sharing immutable data without copy: Arc
-
-To share immutable data between tasks, a first approach would be to only use pipes as we have seen
-previously. A copy of the data to share would then be made for each task. In some cases, this would
-add up to a significant amount of wasted memory and would require copying the same data more than
-necessary.
-
-To tackle this issue, one can use an Atomically Reference Counted wrapper (`Arc`) as implemented in
-the `extra` library of Rust. With an Arc, the data will no longer be copied for each task. The Arc
-acts as a reference to the shared data and only this reference is shared and cloned.
-
-Here is a small example showing how to use Arcs. We wish to run concurrently several computations on
-a single large vector of floats. Each task needs the full vector to perform its duty.
-
-~~~
-# use std::vec;
-# use std::rand;
-use extra::arc::Arc;
-
-fn pnorm(nums: &~[f64], p: uint) -> f64 {
-    nums.iter().fold(0.0, |a,b| a+(*b).powf(&(p as f64)) ).powf(&(1.0 / (p as f64)))
-}
-
-fn main() {
-    let numbers = vec::from_fn(1000000, |_| rand::random::<f64>());
-    println!("Inf-norm = {}",  *numbers.iter().max().unwrap());
-
-    let numbers_arc = Arc::new(numbers);
-
-    for num in range(1u, 10) {
-        let (port, chan)  = Chan::new();
-        chan.send(numbers_arc.clone());
-
-        spawn(proc() {
-            let local_arc : Arc<~[f64]> = port.recv();
-            let task_numbers = local_arc.get();
-            println!("{}-norm = {}", num, pnorm(task_numbers, num));
-        });
-    }
-}
-~~~
-
-The function `pnorm` performs a simple computation on the vector (it computes the sum of its items
-at the power given as argument and takes the inverse power of this value). The Arc on the vector is
-created by the line
-
-~~~
-# use extra::arc::Arc;
-# use std::vec;
-# use std::rand;
-# let numbers = vec::from_fn(1000000, |_| rand::random::<f64>());
-let numbers_arc=Arc::new(numbers);
-~~~
-
-and a clone of it is sent to each task
-
-~~~
-# use extra::arc::Arc;
-# use std::vec;
-# use std::rand;
-# let numbers=vec::from_fn(1000000, |_| rand::random::<f64>());
-# let numbers_arc = Arc::new(numbers);
-# let (port, chan)  = Chan::new();
-chan.send(numbers_arc.clone());
-~~~
-
-copying only the wrapper and not its contents.
-
-Each task recovers the underlying data by
-
-~~~
-# use extra::arc::Arc;
-# use std::vec;
-# use std::rand;
-# let numbers=vec::from_fn(1000000, |_| rand::random::<f64>());
-# let numbers_arc=Arc::new(numbers);
-# let (port, chan)  = Chan::new();
-# chan.send(numbers_arc.clone());
-# let local_arc : Arc<~[f64]> = port.recv();
-let task_numbers = local_arc.get();
-~~~
-
-and can use it as if it were local.
-
-The `arc` module also implements Arcs around mutable data that are not covered here.
-
-# Handling task failure
-
-Rust has a built-in mechanism for raising exceptions. The `fail!()` macro
-(which can also be written with an error string as an argument: `fail!(
-~reason)`) and the `assert!` construct (which effectively calls `fail!()`
-if a boolean expression is false) are both ways to raise exceptions. When a
-task raises an exception the task unwinds its stack---running destructors and
-freeing memory along the way---and then exits. Unlike exceptions in C++,
-exceptions in Rust are unrecoverable within a single task: once a task fails,
-there is no way to "catch" the exception.
-
-While it isn't possible for a task to recover from failure, tasks may notify
-each other of failure. The simplest way of handling task failure is with the
-`try` function, which is similar to `spawn`, but immediately blocks waiting
-for the child task to finish. `try` returns a value of type `Result<T,
-()>`. `Result` is an `enum` type with two variants: `Ok` and `Err`. In this
-case, because the type arguments to `Result` are `int` and `()`, callers can
-pattern-match on a result to check whether it's an `Ok` result with an `int`
-field (representing a successful result) or an `Err` result (representing
-termination with an error).
-
-~~~{.ignore .linked-failure}
-# use std::task;
-# fn some_condition() -> bool { false }
-# fn calculate_result() -> int { 0 }
-let result: Result<int, ()> = task::try(proc() {
-    if some_condition() {
-        calculate_result()
-    } else {
-        fail!("oops!");
-    }
-});
-assert!(result.is_err());
-~~~
-
-Unlike `spawn`, the function spawned using `try` may return a value,
-which `try` will dutifully propagate back to the caller in a [`Result`]
-enum. If the child task terminates successfully, `try` will
-return an `Ok` result; if the child task fails, `try` will return
-an `Error` result.
-
-[`Result`]: std/result/index.html
-
-> ***Note:*** A failed task does not currently produce a useful error
-> value (`try` always returns `Err(())`). In the
-> future, it may be possible for tasks to intercept the value passed to
-> `fail!()`.
-
-TODO: Need discussion of `future_result` in order to make failure
-modes useful.
-
-But not all failures are created equal. In some cases you might need to
-abort the entire program (perhaps you're writing an assert which, if
-it trips, indicates an unrecoverable logic error); in other cases you
-might want to contain the failure at a certain boundary (perhaps a
-small piece of input from the outside world, which you happen to be
-processing in parallel, is malformed and its processing task can't
-proceed).
-
-## Creating a task with a bi-directional communication path
-
-A very common thing to do is to spawn a child task where the parent
-and child both need to exchange messages with each other. The
-function `extra::comm::DuplexStream()` supports this pattern.  We'll
-look briefly at how to use it.
-
-To see how `DuplexStream()` works, we will create a child task
-that repeatedly receives a `uint` message, converts it to a string, and sends
-the string in response.  The child terminates when it receives `0`.
-Here is the function that implements the child task:
-
-~~~{.ignore .linked-failure}
-# use extra::comm::DuplexStream;
-# use std::uint;
-fn stringifier(channel: &DuplexStream<~str, uint>) {
-    let mut value: uint;
-    loop {
-        value = channel.recv();
-        channel.send(uint::to_str(value));
-        if value == 0 { break; }
-    }
-}
-~~~~
-
-The implementation of `DuplexStream` supports both sending and
-receiving. The `stringifier` function takes a `DuplexStream` that can
-send strings (the first type parameter) and receive `uint` messages
-(the second type parameter). The body itself simply loops, reading
-from the channel and then sending its response back.  The actual
-response itself is simply the stringified version of the received value,
-`uint::to_str(value)`.
-
-Here is the code for the parent task:
-
-~~~{.ignore .linked-failure}
-# use std::task::spawn;
-# use std::uint;
-# use extra::comm::DuplexStream;
-# fn stringifier(channel: &DuplexStream<~str, uint>) {
-#     let mut value: uint;
-#     loop {
-#         value = channel.recv();
-#         channel.send(uint::to_str(value));
-#         if value == 0u { break; }
-#     }
-# }
-# fn main() {
-
-let (from_child, to_child) = DuplexStream::new();
-
-spawn(proc() {
-    stringifier(&to_child);
-});
-
-from_child.send(22);
-assert!(from_child.recv() == ~"22");
-
-from_child.send(23);
-from_child.send(0);
-
-assert!(from_child.recv() == ~"23");
-assert!(from_child.recv() == ~"0");
-
-# }
-~~~~
-
-The parent task first calls `DuplexStream` to create a pair of bidirectional
-endpoints. It then uses `task::spawn` to create the child task, which captures
-one end of the communication channel.  As a result, both parent and child can
-send and receive data to and from the other.