rollup merge of #20248: steveklabnik/gh20038
A part of #20038 This is just the beginning of what needs to be done, but it's some of it. /cc @aturon
This commit is contained in:
commit
4717f07989
|
@ -1,6 +1,6 @@
|
|||
% The Rust Tasks and Communication Guide
|
||||
% The Rust Threads and Communication Guide
|
||||
|
||||
**NOTE** This guide is badly out of date an needs to be rewritten.
|
||||
**NOTE** This guide is badly out of date and needs to be rewritten.
|
||||
|
||||
# Introduction
|
||||
|
||||
|
@ -9,36 +9,36 @@ primitives. 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.
|
||||
|
||||
Tasks provide failure isolation and recovery. When a fatal error occurs in Rust
|
||||
Threads provide failure isolation and recovery. When a fatal error occurs in Rust
|
||||
code as a result of an explicit call to `panic!()`, an assertion failure, or
|
||||
another invalid operation, the runtime system destroys the entire task. Unlike
|
||||
another invalid operation, the runtime system destroys the entire thread. Unlike
|
||||
in languages such as Java and C++, there is no way to `catch` an exception.
|
||||
Instead, tasks may monitor each other to see if they panic.
|
||||
Instead, threads may monitor each other to see if they panic.
|
||||
|
||||
Tasks use Rust's type system to provide strong memory safety guarantees. In
|
||||
particular, the type system guarantees that tasks cannot induce a data race
|
||||
Threads use Rust's type system to provide strong memory safety guarantees. In
|
||||
particular, the type system guarantees that threads cannot induce a data race
|
||||
from shared mutable state.
|
||||
|
||||
# Basics
|
||||
|
||||
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.
|
||||
At its simplest, creating a thread is a matter of calling the `spawn` function
|
||||
with a closure argument. `spawn` executes the closure in the new thread.
|
||||
|
||||
```{rust,ignore}
|
||||
# use std::task::spawn;
|
||||
# use std::thread::spawn;
|
||||
|
||||
// Print something profound in a different task using a named function
|
||||
fn print_message() { println!("I am running in a different task!"); }
|
||||
// Print something profound in a different thread using a named function
|
||||
fn print_message() { println!("I am running in a different thread!"); }
|
||||
spawn(print_message);
|
||||
|
||||
// Alternatively, use a `move ||` expression instead of a named function.
|
||||
// `||` expressions evaluate to an unnamed closure. The `move` keyword
|
||||
// indicates that the closure should take ownership of any variables it
|
||||
// touches.
|
||||
spawn(move || println!("I am also running in a different task!"));
|
||||
spawn(move || println!("I am also running in a different thread!"));
|
||||
```
|
||||
|
||||
In Rust, a task is not a concept that appears in the language semantics.
|
||||
In Rust, a thread 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, ownership. The language leaves the implementation
|
||||
details to the standard library.
|
||||
|
@ -49,26 +49,26 @@ argument a closure (of type `F`) that it will run exactly once. This
|
|||
closure is limited to capturing `Send`-able data from its environment
|
||||
(that is, data which is deeply owned). Limiting the closure to `Send`
|
||||
ensures that `spawn` can safely move the entire closure and all its
|
||||
associated state into an entirely different task for execution.
|
||||
associated state into an entirely different thread for execution.
|
||||
|
||||
```{rust,ignore}
|
||||
# use std::task::spawn;
|
||||
# fn generate_task_number() -> int { 0 }
|
||||
# use std::thread::spawn;
|
||||
# fn generate_thread_number() -> int { 0 }
|
||||
// Generate some state locally
|
||||
let child_task_number = generate_task_number();
|
||||
let child_thread_number = generate_thread_number();
|
||||
|
||||
spawn(move || {
|
||||
// Capture it in the remote task. The `move` keyword indicates
|
||||
// that this closure should move `child_task_number` into its
|
||||
// Capture it in the remote thread. The `move` keyword indicates
|
||||
// that this closure should move `child_thread_number` into its
|
||||
// environment, rather than capturing a reference into the
|
||||
// enclosing stack frame.
|
||||
println!("I am child number {}", child_task_number);
|
||||
println!("I am child number {}", child_thread_number);
|
||||
});
|
||||
```
|
||||
|
||||
## Communication
|
||||
|
||||
Now that we have spawned a new task, it would be nice if we could communicate
|
||||
Now that we have spawned a new thread, it would be nice if we could communicate
|
||||
with it. For this, we use *channels*. A channel is simply a pair of endpoints:
|
||||
one for sending messages and another for receiving messages.
|
||||
|
||||
|
@ -78,7 +78,7 @@ of a channel, and a **receiver** is the receiving endpoint. Consider the followi
|
|||
example of calculating two results concurrently:
|
||||
|
||||
```{rust,ignore}
|
||||
# use std::task::spawn;
|
||||
# use std::thread::spawn;
|
||||
|
||||
let (tx, rx): (Sender<int>, Receiver<int>) = channel();
|
||||
|
||||
|
@ -102,12 +102,12 @@ into its component parts).
|
|||
let (tx, rx): (Sender<int>, Receiver<int>) = channel();
|
||||
```
|
||||
|
||||
The child task will use the sender to send data to the parent task, which will
|
||||
The child thread will use the sender to send data to the parent thread, which will
|
||||
wait to receive the data on the receiver. The next statement spawns the child
|
||||
task.
|
||||
thread.
|
||||
|
||||
```{rust,ignore}
|
||||
# use std::task::spawn;
|
||||
# use std::thread::spawn;
|
||||
# fn some_expensive_computation() -> int { 42 }
|
||||
# let (tx, rx) = channel();
|
||||
spawn(move || {
|
||||
|
@ -116,10 +116,10 @@ spawn(move || {
|
|||
});
|
||||
```
|
||||
|
||||
Notice that the creation of the task closure transfers `tx` to the child task
|
||||
Notice that the creation of the thread closure transfers `tx` to the child thread
|
||||
implicitly: the closure captures `tx` in its environment. Both `Sender` and
|
||||
`Receiver` are sendable types and may be captured into tasks or otherwise
|
||||
transferred between them. In the example, the child task runs an expensive
|
||||
`Receiver` are sendable types and may be captured into threads or otherwise
|
||||
transferred between them. In the example, the child thread runs an expensive
|
||||
computation, then sends the result over the captured channel.
|
||||
|
||||
Finally, the parent continues with some other expensive computation, then waits
|
||||
|
@ -137,7 +137,7 @@ The `Sender` and `Receiver` pair created by `channel` enables efficient
|
|||
communication between a single sender and a single receiver, but multiple
|
||||
senders cannot use a single `Sender` value, and multiple receivers cannot use a
|
||||
single `Receiver` value. What if our example needed to compute multiple
|
||||
results across a number of tasks? The following program is ill-typed:
|
||||
results across a number of threads? The following program is ill-typed:
|
||||
|
||||
```{rust,ignore}
|
||||
# fn some_expensive_computation() -> int { 42 }
|
||||
|
@ -160,7 +160,7 @@ Instead we can clone the `tx`, which allows for multiple senders.
|
|||
let (tx, rx) = channel();
|
||||
|
||||
for init_val in range(0u, 3) {
|
||||
// Create a new channel handle to distribute to the child task
|
||||
// Create a new channel handle to distribute to the child thread
|
||||
let child_tx = tx.clone();
|
||||
spawn(move || {
|
||||
child_tx.send(some_expensive_computation(init_val));
|
||||
|
@ -172,7 +172,7 @@ let result = rx.recv() + rx.recv() + rx.recv();
|
|||
```
|
||||
|
||||
Cloning a `Sender` produces a new handle to the same channel, allowing multiple
|
||||
tasks to send data to a single receiver. It upgrades the channel internally in
|
||||
threads to send data to a single receiver. It upgrades the channel internally in
|
||||
order to allow this functionality, which means that channels that are not
|
||||
cloned can avoid the overhead required to handle multiple senders. But this
|
||||
fact has no bearing on the channel's usage: the upgrade is transparent.
|
||||
|
@ -182,9 +182,9 @@ simply use three `Sender` pairs, but it serves to illustrate the point. For
|
|||
reference, written with multiple streams, it might look like the example below.
|
||||
|
||||
```{rust,ignore}
|
||||
# use std::task::spawn;
|
||||
# use std::thread::spawn;
|
||||
|
||||
// Create a vector of ports, one for each child task
|
||||
// Create a vector of ports, one for each child thread
|
||||
let rxs = Vec::from_fn(3, |init_val| {
|
||||
let (tx, rx) = channel();
|
||||
spawn(move || {
|
||||
|
@ -256,18 +256,18 @@ fn main() {
|
|||
|
||||
## Sharing without copying: Arc
|
||||
|
||||
To share data between tasks, a first approach would be to only use channel as
|
||||
To share data between threads, a first approach would be to only use channel 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
|
||||
each thread. 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 `sync` library of Rust. With an Arc, the data
|
||||
will no longer be copied for each task. The Arc acts as a reference to the
|
||||
will no longer be copied for each thread. 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
|
||||
several computations on a single large vector of floats. Each thread needs the
|
||||
full vector to perform its duty.
|
||||
|
||||
```{rust,ignore}
|
||||
|
@ -284,10 +284,10 @@ fn main() {
|
|||
let numbers_arc = Arc::new(numbers);
|
||||
|
||||
for num in range(1u, 10) {
|
||||
let task_numbers = numbers_arc.clone();
|
||||
let thread_numbers = numbers_arc.clone();
|
||||
|
||||
spawn(move || {
|
||||
println!("{}-norm = {}", num, pnorm(task_numbers.as_slice(), num));
|
||||
println!("{}-norm = {}", num, pnorm(thread_numbers.as_slice(), num));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -306,8 +306,8 @@ let numbers_arc = Arc::new(numbers);
|
|||
# }
|
||||
```
|
||||
|
||||
and a clone is captured for each task via a procedure. This only copies
|
||||
the wrapper and not its contents. Within the task's procedure, the captured
|
||||
and a clone is captured for each thread via a procedure. This only copies
|
||||
the wrapper and not its contents. Within the thread's procedure, the captured
|
||||
Arc reference can be used as a shared reference to the underlying vector as
|
||||
if it were local.
|
||||
|
||||
|
@ -319,29 +319,29 @@ if it were local.
|
|||
# let numbers=Vec::from_fn(1000000, |_| rand::random::<f64>());
|
||||
# let numbers_arc = Arc::new(numbers);
|
||||
# let num = 4;
|
||||
let task_numbers = numbers_arc.clone();
|
||||
let thread_numbers = numbers_arc.clone();
|
||||
spawn(move || {
|
||||
// Capture task_numbers and use it as if it was the underlying vector
|
||||
println!("{}-norm = {}", num, pnorm(task_numbers.as_slice(), num));
|
||||
// Capture thread_numbers and use it as if it was the underlying vector
|
||||
println!("{}-norm = {}", num, pnorm(thread_numbers.as_slice(), num));
|
||||
});
|
||||
# }
|
||||
```
|
||||
|
||||
# Handling task panics
|
||||
# Handling thread panics
|
||||
|
||||
Rust has a built-in mechanism for raising exceptions. The `panic!()` macro
|
||||
(which can also be written with an error string as an argument: `panic!(
|
||||
~reason)`) and the `assert!` construct (which effectively calls `panic!()` 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
|
||||
boolean expression is false) are both ways to raise exceptions. When a thread
|
||||
raises an exception, the thread 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 panics,
|
||||
exceptions in Rust are unrecoverable within a single thread: once a thread panics,
|
||||
there is no way to "catch" the exception.
|
||||
|
||||
While it isn't possible for a task to recover from panicking, tasks may notify
|
||||
While it isn't possible for a thread to recover from panicking, threads may notify
|
||||
each other if they panic. The simplest way of handling a panic is with the
|
||||
`try` function, which is similar to `spawn`, but immediately blocks and waits
|
||||
for the child task to finish. `try` returns a value of type
|
||||
for the child thread to finish. `try` returns a value of type
|
||||
`Result<T, Box<Any + Send>>`. `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`
|
||||
|
@ -364,14 +364,14 @@ 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 panics, `try` will return an `Error` result.
|
||||
child thread terminates successfully, `try` will return an `Ok` result; if the
|
||||
child thread panics, `try` will return an `Error` result.
|
||||
|
||||
[`Result`]: std/result/index.html
|
||||
|
||||
> *Note:* A panicked task does not currently produce a useful error
|
||||
> *Note:* A panicked thread 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
|
||||
> future, it may be possible for threads to intercept the value passed to
|
||||
> `panic!()`.
|
||||
|
||||
But not all panics are created equal. In some cases you might need to abort
|
||||
|
@ -379,4 +379,4 @@ 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 panic at a certain boundary (perhaps a small piece of input from
|
||||
the outside world, which you happen to be processing in parallel, is malformed
|
||||
such that the processing task cannot proceed).
|
||||
such that the processing thread cannot proceed).
|
||||
|
|
|
@ -3032,7 +3032,7 @@ test foo ... FAILED
|
|||
failures:
|
||||
|
||||
---- foo stdout ----
|
||||
task 'foo' failed at 'assertion failed: false', /home/you/projects/testing/tests/lib.rs:3
|
||||
thread 'foo' failed at 'assertion failed: false', /home/you/projects/testing/tests/lib.rs:3
|
||||
|
||||
|
||||
|
||||
|
@ -3041,7 +3041,7 @@ failures:
|
|||
|
||||
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured
|
||||
|
||||
task '<main>' failed at 'Some tests failed', /home/you/src/rust/src/libtest/lib.rs:243
|
||||
thread '<main>' failed at 'Some tests failed', /home/you/src/rust/src/libtest/lib.rs:243
|
||||
```
|
||||
|
||||
Lots of output! Let's break this down:
|
||||
|
@ -3084,7 +3084,7 @@ failed, especially as we accumulate more tests.
|
|||
failures:
|
||||
|
||||
---- foo stdout ----
|
||||
task 'foo' failed at 'assertion failed: false', /home/you/projects/testing/tests/lib.rs:3
|
||||
thread 'foo' failed at 'assertion failed: false', /home/you/projects/testing/tests/lib.rs:3
|
||||
|
||||
|
||||
|
||||
|
@ -3093,7 +3093,7 @@ failures:
|
|||
|
||||
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured
|
||||
|
||||
task '<main>' failed at 'Some tests failed', /home/you/src/rust/src/libtest/lib.rs:243
|
||||
thread '<main>' failed at 'Some tests failed', /home/you/src/rust/src/libtest/lib.rs:243
|
||||
```
|
||||
|
||||
After all the tests run, Rust will show us any output from our failed tests.
|
||||
|
@ -4259,7 +4259,7 @@ is that a moving closure always takes ownership of all variables that
|
|||
it uses. Ordinary closures, in contrast, just create a reference into
|
||||
the enclosing stack frame. Moving closures are most useful with Rust's
|
||||
concurrency features, and so we'll just leave it at this for
|
||||
now. We'll talk about them more in the "Tasks" section of the guide.
|
||||
now. We'll talk about them more in the "Threads" section of the guide.
|
||||
|
||||
## Accepting closures as arguments
|
||||
|
||||
|
@ -5209,9 +5209,7 @@ as you can see, there's no overhead of deciding which version to call here,
|
|||
hence 'statically dispatched'. The downside is that we have two copies of
|
||||
the same function, so our binary is a little bit larger.
|
||||
|
||||
# Tasks
|
||||
|
||||
**NOTE**: this section is currently out of date and will be rewritten soon.
|
||||
# Threads
|
||||
|
||||
Concurrency and parallelism are topics that are of increasing interest to a
|
||||
broad subsection of software developers. Modern computers are often multi-core,
|
||||
|
@ -5220,24 +5218,22 @@ processor. Rust's semantics lend themselves very nicely to solving a number of
|
|||
issues that programmers have with concurrency. Many concurrency errors that are
|
||||
runtime errors in other languages are compile-time errors in Rust.
|
||||
|
||||
Rust's concurrency primitive is called a **task**. Tasks are similar to
|
||||
threads, and do not share memory in an unsafe manner, preferring message
|
||||
passing to communicate. It's worth noting that tasks are implemented as a
|
||||
library, and not part of the language. This means that in the future, other
|
||||
concurrency libraries can be written for Rust to help in specific scenarios.
|
||||
Here's an example of creating a task:
|
||||
Rust's concurrency primitive is called a **thread**. It's worth noting that
|
||||
threads are implemented as a library, and not part of the language. This means
|
||||
that in the future, other concurrency libraries can be written for Rust to help
|
||||
in specific scenarios. Here's an example of creating a thread:
|
||||
|
||||
```{rust,ignore}
|
||||
spawn(move || {
|
||||
println!("Hello from a task!");
|
||||
println!("Hello from a thread!");
|
||||
});
|
||||
```
|
||||
|
||||
The `spawn` function takes a closure as an argument, and runs that
|
||||
closure in a new task. Typically, you will want to use a moving
|
||||
closure in a new thread. Typically, you will want to use a moving
|
||||
closure, so that the closure takes ownership of any variables that it
|
||||
touches. This implies that those variables are not usable from the
|
||||
parent task after the child task is spawned:
|
||||
parent thread after the child thread is spawned:
|
||||
|
||||
```{rust,ignore}
|
||||
let mut x = vec![1i, 2i, 3i];
|
||||
|
@ -5253,15 +5249,15 @@ println!("The value of x[0] is: {}", x[0]); // error: use of moved value: `x`
|
|||
other languages would let us do this, but it's not safe to do
|
||||
so. Rust's borrow checker catches the error.
|
||||
|
||||
If tasks were only able to capture these values, they wouldn't be very useful.
|
||||
Luckily, tasks can communicate with each other through **channel**s. Channels
|
||||
If threads were only able to capture these values, they wouldn't be very useful.
|
||||
Luckily, threads can communicate with each other through **channel**s. Channels
|
||||
work like this:
|
||||
|
||||
```{rust,ignore}
|
||||
let (tx, rx) = channel();
|
||||
|
||||
spawn(move || {
|
||||
tx.send("Hello from a task!".to_string());
|
||||
tx.send("Hello from a thread!".to_string());
|
||||
});
|
||||
|
||||
let message = rx.recv();
|
||||
|
@ -5274,14 +5270,14 @@ receive the message on the `Receiver<T>` side with the `recv()` method. This
|
|||
method blocks until it gets a message. There's a similar method, `.try_recv()`,
|
||||
which returns an `Result<T, TryRecvError>` and does not block.
|
||||
|
||||
If you want to send messages to the task as well, create two channels!
|
||||
If you want to send messages to the thread as well, create two channels!
|
||||
|
||||
```{rust,ignore}
|
||||
let (tx1, rx1) = channel();
|
||||
let (tx2, rx2) = channel();
|
||||
|
||||
spawn(move || {
|
||||
tx1.send("Hello from a task!".to_string());
|
||||
tx1.send("Hello from a thread!".to_string());
|
||||
let message = rx2.recv();
|
||||
println!("{}", message);
|
||||
});
|
||||
|
@ -5292,9 +5288,9 @@ println!("{}", message);
|
|||
tx2.send("Goodbye from main!".to_string());
|
||||
```
|
||||
|
||||
The closure has one sending end and one receiving end, and the main
|
||||
task has one of each as well. Now they can talk back and forth in
|
||||
whatever way they wish.
|
||||
The closure has one sending end and one receiving end, and the main thread has
|
||||
one of each as well. Now they can talk back and forth in whatever way they
|
||||
wish.
|
||||
|
||||
Notice as well that because `Sender` and `Receiver` are generic, while you can
|
||||
pass any kind of information through the channel, the ends are strongly typed.
|
||||
|
@ -5333,7 +5329,7 @@ we'll just get the value immediately.
|
|||
|
||||
## Success and failure
|
||||
|
||||
Tasks don't always succeed, they can also panic. A task that wishes to panic
|
||||
Threads don't always succeed, they can also panic. A thread that wishes to panic
|
||||
can call the `panic!` macro, passing a message:
|
||||
|
||||
```{rust,ignore}
|
||||
|
@ -5342,14 +5338,14 @@ spawn(move || {
|
|||
});
|
||||
```
|
||||
|
||||
If a task panics, it is not possible for it to recover. However, it can
|
||||
notify other tasks that it has panicked. We can do this with `task::try`:
|
||||
If a thread panics, it is not possible for it to recover. However, it can
|
||||
notify other thread that it has panicked. We can do this with `thread::try`:
|
||||
|
||||
```{rust,ignore}
|
||||
use std::task;
|
||||
use std::thread;
|
||||
use std::rand;
|
||||
|
||||
let result = task::try(move || {
|
||||
let result = thread::try(move || {
|
||||
if rand::random() {
|
||||
println!("OK");
|
||||
} else {
|
||||
|
@ -5358,7 +5354,7 @@ let result = task::try(move || {
|
|||
});
|
||||
```
|
||||
|
||||
This task will randomly panic or succeed. `task::try` returns a `Result`
|
||||
This thread will randomly panic or succeed. `thread::try` returns a `Result`
|
||||
type, so we can handle the response like any other computation that may
|
||||
fail.
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ a guide that can help you out:
|
|||
* [Strings](guide-strings.html)
|
||||
* [Pointers](guide-pointers.html)
|
||||
* [Crates and modules](guide-crates.html)
|
||||
* [Tasks and Communication](guide-tasks.html)
|
||||
* [Threads and Communication](guide-threads.html)
|
||||
* [Error Handling](guide-error-handling.html)
|
||||
* [Foreign Function Interface](guide-ffi.html)
|
||||
* [Writing Unsafe and Low-Level Code](guide-unsafe.html)
|
||||
|
|
|
@ -899,8 +899,8 @@ mirrors the module hierarchy.
|
|||
// Load the `vec` module from `vec.rs`
|
||||
mod vec;
|
||||
|
||||
mod task {
|
||||
// Load the `local_data` module from `task/local_data.rs`
|
||||
mod thread {
|
||||
// Load the `local_data` module from `thread/local_data.rs`
|
||||
mod local_data;
|
||||
}
|
||||
```
|
||||
|
@ -909,9 +909,9 @@ The directories and files used for loading external file modules can be
|
|||
influenced with the `path` attribute.
|
||||
|
||||
```{.ignore}
|
||||
#[path = "task_files"]
|
||||
mod task {
|
||||
// Load the `local_data` module from `task_files/tls.rs`
|
||||
#[path = "thread_files"]
|
||||
mod thread {
|
||||
// Load the `local_data` module from `thread_files/tls.rs`
|
||||
#[path = "tls.rs"]
|
||||
mod local_data;
|
||||
}
|
||||
|
@ -1188,7 +1188,7 @@ code safe, in the surrounding context.
|
|||
Unsafe blocks are used to wrap foreign libraries, make direct use of hardware
|
||||
or implement features not directly present in the language. For example, Rust
|
||||
provides the language features necessary to implement memory-safe concurrency
|
||||
in the language but the implementation of tasks and message passing is in the
|
||||
in the language but the implementation of threads and message passing is in the
|
||||
standard library.
|
||||
|
||||
Rust's type system is a conservative approximation of the dynamic safety
|
||||
|
@ -1500,7 +1500,7 @@ be modified by the program. One of Rust's goals is to make concurrency bugs
|
|||
hard to run into, and this is obviously a very large source of race conditions
|
||||
or other bugs. For this reason, an `unsafe` block is required when either
|
||||
reading or writing a mutable static variable. Care should be taken to ensure
|
||||
that modifications to a mutable static are safe with respect to other tasks
|
||||
that modifications to a mutable static are safe with respect to other threads
|
||||
running in the same process.
|
||||
|
||||
Mutable statics are still very useful, however. They can be used with C
|
||||
|
@ -2253,11 +2253,11 @@ A complete list of the built-in language items follows:
|
|||
* `drop`
|
||||
: Have destructors.
|
||||
* `send`
|
||||
: Able to be sent across task boundaries.
|
||||
: Able to be sent across thread boundaries.
|
||||
* `sized`
|
||||
: Has a size known at compile time.
|
||||
* `sync`
|
||||
: Able to be safely shared between tasks when aliased.
|
||||
: Able to be safely shared between threads when aliased.
|
||||
|
||||
#### Operators
|
||||
|
||||
|
@ -2621,7 +2621,7 @@ The currently implemented features of the reference compiler are:
|
|||
LLVM's implementation which works in concert with the kernel
|
||||
loader and dynamic linker. This is not necessarily available
|
||||
on all platforms, and usage of it is discouraged (rust
|
||||
focuses more on task-local data instead of thread-local
|
||||
focuses more on thread-local data instead of thread-local
|
||||
data).
|
||||
|
||||
* `trace_macros` - Allows use of the `trace_macros` macro, which is a nasty
|
||||
|
@ -2939,7 +2939,7 @@ array is mutable, the resulting [lvalue](#lvalues,-rvalues-and-temporaries) can
|
|||
be assigned to.
|
||||
|
||||
Indices are zero-based, and may be of any integral type. Vector access is
|
||||
bounds-checked at run-time. When the check fails, it will put the task in a
|
||||
bounds-checked at run-time. When the check fails, it will put the thread in a
|
||||
_panicked state_.
|
||||
|
||||
```{should-fail}
|
||||
|
@ -3950,7 +3950,7 @@ Types in Rust are categorized into kinds, based on various properties of the
|
|||
components of the type. The kinds are:
|
||||
|
||||
* `Send`
|
||||
: Types of this kind can be safely sent between tasks.
|
||||
: Types of this kind can be safely sent between threads.
|
||||
This kind includes scalars, boxes, procs, and
|
||||
structural types containing only other owned types.
|
||||
All `Send` types are `'static`.
|
||||
|
@ -3998,21 +3998,21 @@ to sendable.
|
|||
|
||||
# Memory and concurrency models
|
||||
|
||||
Rust has a memory model centered around concurrently-executing _tasks_. Thus
|
||||
Rust has a memory model centered around concurrently-executing _threads_. Thus
|
||||
its memory model and its concurrency model are best discussed simultaneously,
|
||||
as parts of each only make sense when considered from the perspective of the
|
||||
other.
|
||||
|
||||
When reading about the memory model, keep in mind that it is partitioned in
|
||||
order to support tasks; and when reading about tasks, keep in mind that their
|
||||
order to support threads; and when reading about threads, keep in mind that their
|
||||
isolation and communication mechanisms are only possible due to the ownership
|
||||
and lifetime semantics of the memory model.
|
||||
|
||||
## Memory model
|
||||
|
||||
A Rust program's memory consists of a static set of *items*, a set of
|
||||
[tasks](#tasks) each with its own *stack*, and a *heap*. Immutable portions of
|
||||
the heap may be shared between tasks, mutable portions may not.
|
||||
[threads](#threads) each with its own *stack*, and a *heap*. Immutable portions of
|
||||
the heap may be shared between threads, mutable portions may not.
|
||||
|
||||
Allocations in the stack consist of *slots*, and allocations in the heap
|
||||
consist of *boxes*.
|
||||
|
@ -4023,8 +4023,8 @@ The _items_ of a program are those functions, modules and types that have their
|
|||
value calculated at compile-time and stored uniquely in the memory image of the
|
||||
rust process. Items are neither dynamically allocated nor freed.
|
||||
|
||||
A task's _stack_ consists of activation frames automatically allocated on entry
|
||||
to each function as the task executes. A stack allocation is reclaimed when
|
||||
A thread's _stack_ consists of activation frames automatically allocated on entry
|
||||
to each function as the thread executes. A stack allocation is reclaimed when
|
||||
control leaves the frame containing it.
|
||||
|
||||
The _heap_ is a general term that describes boxes. The lifetime of an
|
||||
|
@ -4034,10 +4034,10 @@ in the heap, heap allocations may outlive the frame they are allocated within.
|
|||
|
||||
### Memory ownership
|
||||
|
||||
A task owns all memory it can *safely* reach through local variables, as well
|
||||
A thread owns all memory it can *safely* reach through local variables, as well
|
||||
as boxes and references.
|
||||
|
||||
When a task sends a value that has the `Send` trait to another task, it loses
|
||||
When a thread sends a value that has the `Send` trait to another thread, it loses
|
||||
ownership of the value sent and can no longer refer to it. This is statically
|
||||
guaranteed by the combined use of "move semantics", and the compiler-checked
|
||||
_meaning_ of the `Send` trait: it is only instantiated for (transitively)
|
||||
|
@ -4046,12 +4046,12 @@ sendable kinds of data constructor and pointers, never including references.
|
|||
When a stack frame is exited, its local allocations are all released, and its
|
||||
references to boxes are dropped.
|
||||
|
||||
When a task finishes, its stack is necessarily empty and it therefore has no
|
||||
When a thread finishes, its stack is necessarily empty and it therefore has no
|
||||
references to any boxes; the remainder of its heap is immediately freed.
|
||||
|
||||
### Memory slots
|
||||
|
||||
A task's stack contains slots.
|
||||
A thread's stack contains slots.
|
||||
|
||||
A _slot_ is a component of a stack frame, either a function parameter, a
|
||||
[temporary](#lvalues,-rvalues-and-temporaries), or a local variable.
|
||||
|
@ -4105,72 +4105,69 @@ let y = x;
|
|||
// attempting to use `x` will result in an error here
|
||||
```
|
||||
|
||||
## Tasks
|
||||
## Threads
|
||||
|
||||
An executing Rust program consists of a tree of tasks. A Rust _task_ consists
|
||||
of an entry function, a stack, a set of outgoing communication channels and
|
||||
incoming communication ports, and ownership of some portion of the heap of a
|
||||
single operating-system process.
|
||||
Rust's primary concurrency mechanism is called a **thread**.
|
||||
|
||||
### Communication between tasks
|
||||
### Communication between threads
|
||||
|
||||
Rust tasks are isolated and generally unable to interfere with one another's
|
||||
Rust threads are isolated and generally unable to interfere with one another's
|
||||
memory directly, except through [`unsafe` code](#unsafe-functions). All
|
||||
contact between tasks is mediated by safe forms of ownership transfer, and data
|
||||
contact between threads is mediated by safe forms of ownership transfer, and data
|
||||
races on memory are prohibited by the type system.
|
||||
|
||||
When you wish to send data between tasks, the values are restricted to the
|
||||
When you wish to send data between threads, the values are restricted to the
|
||||
[`Send` type-kind](#type-kinds). Restricting communication interfaces to this
|
||||
kind ensures that no references move between tasks. Thus access to an entire
|
||||
kind ensures that no references move between threads. Thus access to an entire
|
||||
data structure can be mediated through its owning "root" value; no further
|
||||
locking or copying is required to avoid data races within the substructure of
|
||||
such a value.
|
||||
|
||||
### Task lifecycle
|
||||
### Thread
|
||||
|
||||
The _lifecycle_ of a task consists of a finite set of states and events that
|
||||
cause transitions between the states. The lifecycle states of a task are:
|
||||
The _lifecycle_ of a threads consists of a finite set of states and events that
|
||||
cause transitions between the states. The lifecycle states of a thread are:
|
||||
|
||||
* running
|
||||
* blocked
|
||||
* panicked
|
||||
* dead
|
||||
|
||||
A task begins its lifecycle — once it has been spawned — in the
|
||||
A thread begins its lifecycle — once it has been spawned — in the
|
||||
*running* state. In this state it executes the statements of its entry
|
||||
function, and any functions called by the entry function.
|
||||
|
||||
A task may transition from the *running* state to the *blocked* state any time
|
||||
A thread may transition from the *running* state to the *blocked* state any time
|
||||
it makes a blocking communication call. When the call can be completed —
|
||||
when a message arrives at a sender, or a buffer opens to receive a message
|
||||
— then the blocked task will unblock and transition back to *running*.
|
||||
— then the blocked thread will unblock and transition back to *running*.
|
||||
|
||||
A task may transition to the *panicked* state at any time, due being killed by
|
||||
A thread may transition to the *panicked* state at any time, due being killed by
|
||||
some external event or internally, from the evaluation of a `panic!()` macro.
|
||||
Once *panicking*, a task unwinds its stack and transitions to the *dead* state.
|
||||
Unwinding the stack of a task is done by the task itself, on its own control
|
||||
Once *panicking*, a thread unwinds its stack and transitions to the *dead* state.
|
||||
Unwinding the stack of a thread is done by the thread itself, on its own control
|
||||
stack. If a value with a destructor is freed during unwinding, the code for the
|
||||
destructor is run, also on the task's control stack. Running the destructor
|
||||
destructor is run, also on the thread's control stack. Running the destructor
|
||||
code causes a temporary transition to a *running* state, and allows the
|
||||
destructor code to cause any subsequent state transitions. The original task
|
||||
destructor code to cause any subsequent state transitions. The original thread
|
||||
of unwinding and panicking thereby may suspend temporarily, and may involve
|
||||
(recursive) unwinding of the stack of a failed destructor. Nonetheless, the
|
||||
outermost unwinding activity will continue until the stack is unwound and the
|
||||
task transitions to the *dead* state. There is no way to "recover" from task
|
||||
panics. Once a task has temporarily suspended its unwinding in the *panicking*
|
||||
thread transitions to the *dead* state. There is no way to "recover" from thread
|
||||
panics. Once a thread has temporarily suspended its unwinding in the *panicking*
|
||||
state, a panic occurring from within this destructor results in *hard* panic.
|
||||
A hard panic currently results in the process aborting.
|
||||
|
||||
A task in the *dead* state cannot transition to other states; it exists only to
|
||||
have its termination status inspected by other tasks, and/or to await
|
||||
A thread in the *dead* state cannot transition to other states; it exists only to
|
||||
have its termination status inspected by other threads, and/or to await
|
||||
reclamation when the last reference to it drops.
|
||||
|
||||
# Runtime services, linkage and debugging
|
||||
|
||||
The Rust _runtime_ is a relatively compact collection of Rust code that
|
||||
provides fundamental services and datatypes to all Rust tasks at run-time. It
|
||||
provides fundamental services and datatypes to all Rust threads at run-time. It
|
||||
is smaller and simpler than many modern language runtimes. It is tightly
|
||||
integrated into the language's execution model of memory, tasks, communication
|
||||
integrated into the language's execution model of memory, threads, communication
|
||||
and logging.
|
||||
|
||||
### Memory allocation
|
||||
|
@ -4181,7 +4178,7 @@ environment and releases them back to its environment when they are no longer
|
|||
needed. The default implementation of the service-provider interface consists
|
||||
of the C runtime functions `malloc` and `free`.
|
||||
|
||||
The runtime memory-management system, in turn, supplies Rust tasks with
|
||||
The runtime memory-management system, in turn, supplies Rust threads with
|
||||
facilities for allocating releasing stacks, as well as allocating and freeing
|
||||
heap data.
|
||||
|
||||
|
@ -4189,15 +4186,15 @@ heap data.
|
|||
|
||||
The runtime provides C and Rust code to assist with various built-in types,
|
||||
such as arrays, strings, and the low level communication system (ports,
|
||||
channels, tasks).
|
||||
channels, threads).
|
||||
|
||||
Support for other built-in types such as simple types, tuples and enums is
|
||||
open-coded by the Rust compiler.
|
||||
|
||||
### Task scheduling and communication
|
||||
### Thread scheduling and communication
|
||||
|
||||
The runtime provides code to manage inter-task communication. This includes
|
||||
the system of task-lifecycle state transitions depending on the contents of
|
||||
The runtime provides code to manage inter-thread communication. This includes
|
||||
the system of thread-lifecycle state transitions depending on the contents of
|
||||
queues, as well as code to copy values between queues and their recipients and
|
||||
to serialize values for transmission over operating-system inter-process
|
||||
communication facilities.
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
//! Task-local reference-counted boxes (the `Rc<T>` type).
|
||||
//! Thread-local reference-counted boxes (the `Rc<T>` type).
|
||||
//!
|
||||
//! The `Rc<T>` type provides shared ownership of an immutable value. Destruction is deterministic,
|
||||
//! and will occur as soon as the last owner is gone. It is marked as non-sendable because it
|
||||
|
|
|
@ -540,7 +540,7 @@ pub fn monitor<F:FnOnce()+Send>(f: F) {
|
|||
match cfg.spawn(move || { std::io::stdio::set_stderr(box w); f() }).join() {
|
||||
Ok(()) => { /* fallthrough */ }
|
||||
Err(value) => {
|
||||
// Task panicked without emitting a fatal diagnostic
|
||||
// Thread panicked without emitting a fatal diagnostic
|
||||
if !value.is::<diagnostic::FatalError>() {
|
||||
let mut emitter = diagnostic::EmitterWriter::stderr(diagnostic::Auto, None);
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
//! See the `distributions` submodule for sampling random numbers from
|
||||
//! distributions like normal and exponential.
|
||||
//!
|
||||
//! # Task-local RNG
|
||||
//! # Thread-local RNG
|
||||
//!
|
||||
//! There is built-in support for a RNG associated with each task stored
|
||||
//! in task-local storage. This RNG can be accessed via `task_rng`, or
|
||||
|
|
|
@ -53,7 +53,7 @@ pub mod args;
|
|||
mod at_exit_imp;
|
||||
mod libunwind;
|
||||
|
||||
/// The default error code of the rust runtime if the main task panics instead
|
||||
/// The default error code of the rust runtime if the main thread panics instead
|
||||
/// of exiting cleanly.
|
||||
pub const DEFAULT_ERROR_CODE: int = 101;
|
||||
|
||||
|
@ -137,9 +137,9 @@ fn lang_start(main: *const u8, argc: int, argv: *const *const u8) -> int {
|
|||
///
|
||||
/// The procedure passed to this function will be executed as part of the
|
||||
/// runtime cleanup phase. For normal rust programs, this means that it will run
|
||||
/// after all other tasks have exited.
|
||||
/// after all other threads have exited.
|
||||
///
|
||||
/// The procedure is *not* executed with a local `Task` available to it, so
|
||||
/// The procedure is *not* executed with a local `Thread` available to it, so
|
||||
/// primitives like logging, I/O, channels, spawning, etc, are *not* available.
|
||||
/// This is meant for "bare bones" usage to clean up runtime details, this is
|
||||
/// not meant as a general-purpose "let's clean everything up" function.
|
||||
|
|
|
@ -36,7 +36,7 @@ use sys_common::stack;
|
|||
use rt::unwind;
|
||||
use rt::unwind::Unwinder;
|
||||
|
||||
/// State associated with Rust tasks.
|
||||
/// State associated with Rust threads
|
||||
///
|
||||
/// This structure is currently undergoing major changes, and is
|
||||
/// likely to be move/be merged with a `Thread` structure.
|
||||
|
@ -50,14 +50,14 @@ pub struct Task {
|
|||
awoken: bool, // used to prevent spurious wakeups
|
||||
|
||||
// This field holds the known bounds of the stack in (lo, hi) form. Not all
|
||||
// native tasks necessarily know their precise bounds, hence this is
|
||||
// native threads necessarily know their precise bounds, hence this is
|
||||
// optional.
|
||||
stack_bounds: (uint, uint),
|
||||
|
||||
stack_guard: uint
|
||||
}
|
||||
|
||||
// Once a task has entered the `Armed` state it must be destroyed via `drop`,
|
||||
// Once a thread has entered the `Armed` state it must be destroyed via `drop`,
|
||||
// and no other method. This state is used to track this transition.
|
||||
#[deriving(PartialEq)]
|
||||
enum TaskState {
|
||||
|
@ -67,31 +67,31 @@ enum TaskState {
|
|||
}
|
||||
|
||||
pub struct TaskOpts {
|
||||
/// Invoke this procedure with the result of the task when it finishes.
|
||||
/// Invoke this procedure with the result of the thread when it finishes.
|
||||
pub on_exit: Option<Thunk<Result>>,
|
||||
/// A name for the task-to-be, for identification in panic messages
|
||||
/// A name for the thread-to-be, for identification in panic messages
|
||||
pub name: Option<SendStr>,
|
||||
/// The size of the stack for the spawned task
|
||||
/// The size of the stack for the spawned thread
|
||||
pub stack_size: Option<uint>,
|
||||
}
|
||||
|
||||
/// Indicates the manner in which a task exited.
|
||||
/// Indicates the manner in which a thread exited.
|
||||
///
|
||||
/// A task that completes without panicking is considered to exit successfully.
|
||||
/// A thread that completes without panicking is considered to exit successfully.
|
||||
///
|
||||
/// If you wish for this result's delivery to block until all
|
||||
/// children tasks complete, recommend using a result future.
|
||||
/// children threads complete, recommend using a result future.
|
||||
pub type Result = ::core::result::Result<(), Box<Any + Send>>;
|
||||
|
||||
/// A handle to a blocked task. Usually this means having the Box<Task>
|
||||
/// pointer by ownership, but if the task is killable, a killer can steal it
|
||||
/// A handle to a blocked thread. Usually this means having the Box<Task>
|
||||
/// pointer by ownership, but if the thread is killable, a killer can steal it
|
||||
/// at any time.
|
||||
pub enum BlockedTask {
|
||||
Owned(Box<Task>),
|
||||
Shared(Arc<AtomicUint>),
|
||||
}
|
||||
|
||||
/// Per-task state related to task death, killing, panic, etc.
|
||||
/// Per-thread state related to thread death, killing, panic, etc.
|
||||
pub struct Death {
|
||||
pub on_exit: Option<Thunk<Result>>,
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ pub struct BlockedTasks {
|
|||
}
|
||||
|
||||
impl Task {
|
||||
/// Creates a new uninitialized task.
|
||||
/// Creates a new uninitialized thread.
|
||||
pub fn new(stack_bounds: Option<(uint, uint)>, stack_guard: Option<uint>) -> Task {
|
||||
Task {
|
||||
unwinder: Unwinder::new(),
|
||||
|
@ -153,17 +153,17 @@ impl Task {
|
|||
})
|
||||
}
|
||||
|
||||
/// Consumes ownership of a task, runs some code, and returns the task back.
|
||||
/// Consumes ownership of a thread, runs some code, and returns the thread back.
|
||||
///
|
||||
/// This function can be used as an emulated "try/catch" to interoperate
|
||||
/// with the rust runtime at the outermost boundary. It is not possible to
|
||||
/// use this function in a nested fashion (a try/catch inside of another
|
||||
/// try/catch). Invoking this function is quite cheap.
|
||||
///
|
||||
/// If the closure `f` succeeds, then the returned task can be used again
|
||||
/// If the closure `f` succeeds, then the returned thread can be used again
|
||||
/// for another invocation of `run`. If the closure `f` panics then `self`
|
||||
/// will be internally destroyed along with all of the other associated
|
||||
/// resources of this task. The `on_exit` callback is invoked with the
|
||||
/// resources of this thread. The `on_exit` callback is invoked with the
|
||||
/// cause of panic (not returned here). This can be discovered by querying
|
||||
/// `is_destroyed()`.
|
||||
///
|
||||
|
@ -172,30 +172,30 @@ impl Task {
|
|||
/// guaranteed to return if it panicks. Care should be taken to ensure that
|
||||
/// stack references made by `f` are handled appropriately.
|
||||
///
|
||||
/// It is invalid to call this function with a task that has been previously
|
||||
/// It is invalid to call this function with a thread that has been previously
|
||||
/// destroyed via a failed call to `run`.
|
||||
pub fn run(mut self: Box<Task>, f: ||) -> Box<Task> {
|
||||
assert!(!self.is_destroyed(), "cannot re-use a destroyed task");
|
||||
assert!(!self.is_destroyed(), "cannot re-use a destroyed thread");
|
||||
|
||||
// First, make sure that no one else is in TLS. This does not allow
|
||||
// recursive invocations of run(). If there's no one else, then
|
||||
// relinquish ownership of ourselves back into TLS.
|
||||
if Local::exists(None::<Task>) {
|
||||
panic!("cannot run a task recursively inside another");
|
||||
panic!("cannot run a thread recursively inside another");
|
||||
}
|
||||
self.state = Armed;
|
||||
Local::put(self);
|
||||
|
||||
// There are two primary reasons that general try/catch is unsafe. The
|
||||
// first is that we do not support nested try/catch. The above check for
|
||||
// an existing task in TLS is sufficient for this invariant to be
|
||||
// an existing thread in TLS is sufficient for this invariant to be
|
||||
// upheld. The second is that unwinding while unwinding is not defined.
|
||||
// We take care of that by having an 'unwinding' flag in the task
|
||||
// We take care of that by having an 'unwinding' flag in the thread
|
||||
// itself. For these reasons, this unsafety should be ok.
|
||||
let result = unsafe { unwind::try(f) };
|
||||
|
||||
// After running the closure given return the task back out if it ran
|
||||
// successfully, or clean up the task if it panicked.
|
||||
// After running the closure given return the thread back out if it ran
|
||||
// successfully, or clean up the thread if it panicked.
|
||||
let task: Box<Task> = Local::take();
|
||||
match result {
|
||||
Ok(()) => task,
|
||||
|
@ -203,13 +203,13 @@ impl Task {
|
|||
}
|
||||
}
|
||||
|
||||
/// Destroy all associated resources of this task.
|
||||
/// Destroy all associated resources of this thread.
|
||||
///
|
||||
/// This function will perform any necessary clean up to prepare the task
|
||||
/// This function will perform any necessary clean up to prepare the thread
|
||||
/// for destruction. It is required that this is called before a `Task`
|
||||
/// falls out of scope.
|
||||
///
|
||||
/// The returned task cannot be used for running any more code, but it may
|
||||
/// The returned thread cannot be used for running any more code, but it may
|
||||
/// be used to extract the runtime as necessary.
|
||||
pub fn destroy(self: Box<Task>) -> Box<Task> {
|
||||
if self.is_destroyed() {
|
||||
|
@ -219,14 +219,14 @@ impl Task {
|
|||
}
|
||||
}
|
||||
|
||||
/// Cleans up a task, processing the result of the task as appropriate.
|
||||
/// Cleans up a thread, processing the result of the thread as appropriate.
|
||||
///
|
||||
/// This function consumes ownership of the task, deallocating it once it's
|
||||
/// This function consumes ownership of the thread, deallocating it once it's
|
||||
/// done being processed. It is assumed that TLD and the local heap have
|
||||
/// already been destroyed and/or annihilated.
|
||||
fn cleanup(mut self: Box<Task>, result: Result) -> Box<Task> {
|
||||
// After taking care of the data above, we need to transmit the result
|
||||
// of this task.
|
||||
// of this thread.
|
||||
let what_to_do = self.death.on_exit.take();
|
||||
Local::put(self);
|
||||
|
||||
|
@ -235,15 +235,15 @@ impl Task {
|
|||
// if this panics, this will also likely abort the runtime.
|
||||
//
|
||||
// This closure is currently limited to a channel send via the
|
||||
// standard library's task interface, but this needs
|
||||
// standard library's thread interface, but this needs
|
||||
// reconsideration to whether it's a reasonable thing to let a
|
||||
// task to do or not.
|
||||
// thread to do or not.
|
||||
match what_to_do {
|
||||
Some(f) => { f.invoke(result) }
|
||||
None => { drop(result) }
|
||||
}
|
||||
|
||||
// Now that we're done, we remove the task from TLS and flag it for
|
||||
// Now that we're done, we remove the thread from TLS and flag it for
|
||||
// destruction.
|
||||
let mut task: Box<Task> = Local::take();
|
||||
task.state = Destroyed;
|
||||
|
@ -253,7 +253,7 @@ impl Task {
|
|||
/// Queries whether this can be destroyed or not.
|
||||
pub fn is_destroyed(&self) -> bool { self.state == Destroyed }
|
||||
|
||||
/// Deschedules the current task, invoking `f` `amt` times. It is not
|
||||
/// Deschedules the current thread, invoking `f` `amt` times. It is not
|
||||
/// recommended to use this function directly, but rather communication
|
||||
/// primitives in `std::comm` should be used.
|
||||
//
|
||||
|
@ -262,31 +262,31 @@ impl Task {
|
|||
// shared state. Additionally, all of the violations are protected with a
|
||||
// mutex, so in theory there are no races.
|
||||
//
|
||||
// The first thing we need to do is to get a pointer to the task's internal
|
||||
// mutex. This address will not be changing (because the task is allocated
|
||||
// on the heap). We must have this handle separately because the task will
|
||||
// The first thing we need to do is to get a pointer to the thread's internal
|
||||
// mutex. This address will not be changing (because the thread is allocated
|
||||
// on the heap). We must have this handle separately because the thread will
|
||||
// have its ownership transferred to the given closure. We're guaranteed,
|
||||
// however, that this memory will remain valid because *this* is the current
|
||||
// task's execution thread.
|
||||
// thread's execution thread.
|
||||
//
|
||||
// The next weird part is where ownership of the task actually goes. We
|
||||
// The next weird part is where ownership of the thread actually goes. We
|
||||
// relinquish it to the `f` blocking function, but upon returning this
|
||||
// function needs to replace the task back in TLS. There is no communication
|
||||
// from the wakeup thread back to this thread about the task pointer, and
|
||||
// there's really no need to. In order to get around this, we cast the task
|
||||
// function needs to replace the thread back in TLS. There is no communication
|
||||
// from the wakeup thread back to this thread about the thread pointer, and
|
||||
// there's really no need to. In order to get around this, we cast the thread
|
||||
// to a `uint` which is then used at the end of this function to cast back
|
||||
// to a `Box<Task>` object. Naturally, this looks like it violates
|
||||
// ownership semantics in that there may be two `Box<Task>` objects.
|
||||
//
|
||||
// The fun part is that the wakeup half of this implementation knows to
|
||||
// "forget" the task on the other end. This means that the awakening half of
|
||||
// "forget" the thread on the other end. This means that the awakening half of
|
||||
// things silently relinquishes ownership back to this thread, but not in a
|
||||
// way that the compiler can understand. The task's memory is always valid
|
||||
// for both tasks because these operations are all done inside of a mutex.
|
||||
// way that the compiler can understand. The thread's memory is always valid
|
||||
// for both threads because these operations are all done inside of a mutex.
|
||||
//
|
||||
// You'll also find that if blocking fails (the `f` function hands the
|
||||
// BlockedTask back to us), we will `mem::forget` the handles. The
|
||||
// reasoning for this is the same logic as above in that the task silently
|
||||
// reasoning for this is the same logic as above in that the thread silently
|
||||
// transfers ownership via the `uint`, not through normal compiler
|
||||
// semantics.
|
||||
//
|
||||
|
@ -319,11 +319,11 @@ impl Task {
|
|||
let guard = (*me).lock.lock();
|
||||
(*me).awoken = false;
|
||||
|
||||
// Apply the given closure to all of the "selectable tasks",
|
||||
// Apply the given closure to all of the "selectable threads",
|
||||
// bailing on the first one that produces an error. Note that
|
||||
// care must be taken such that when an error is occurred, we
|
||||
// may not own the task, so we may still have to wait for the
|
||||
// task to become available. In other words, if task.wake()
|
||||
// may not own the thread, so we may still have to wait for the
|
||||
// thread to become available. In other words, if thread.wake()
|
||||
// returns `None`, then someone else has ownership and we must
|
||||
// wait for their signal.
|
||||
match iter.map(f).filter_map(|a| a.err()).next() {
|
||||
|
@ -342,15 +342,15 @@ impl Task {
|
|||
guard.wait();
|
||||
}
|
||||
}
|
||||
// put the task back in TLS, and everything is as it once was.
|
||||
// put the thread back in TLS, and everything is as it once was.
|
||||
Local::put(mem::transmute(me));
|
||||
}
|
||||
}
|
||||
|
||||
/// Wakes up a previously blocked task. This function can only be
|
||||
/// called on tasks that were previously blocked in `deschedule`.
|
||||
/// Wakes up a previously blocked thread. This function can only be
|
||||
/// called on threads that were previously blocked in `deschedule`.
|
||||
//
|
||||
// See the comments on `deschedule` for why the task is forgotten here, and
|
||||
// See the comments on `deschedule` for why the thread is forgotten here, and
|
||||
// why it's valid to do so.
|
||||
pub fn reawaken(mut self: Box<Task>) {
|
||||
unsafe {
|
||||
|
@ -362,21 +362,21 @@ impl Task {
|
|||
}
|
||||
}
|
||||
|
||||
/// Yields control of this task to another task. This function will
|
||||
/// Yields control of this thread to another thread. This function will
|
||||
/// eventually return, but possibly not immediately. This is used as an
|
||||
/// opportunity to allow other tasks a chance to run.
|
||||
/// opportunity to allow other threads a chance to run.
|
||||
pub fn yield_now() {
|
||||
Thread::yield_now();
|
||||
}
|
||||
|
||||
/// Returns the stack bounds for this task in (lo, hi) format. The stack
|
||||
/// bounds may not be known for all tasks, so the return value may be
|
||||
/// Returns the stack bounds for this thread in (lo, hi) format. The stack
|
||||
/// bounds may not be known for all threads, so the return value may be
|
||||
/// `None`.
|
||||
pub fn stack_bounds(&self) -> (uint, uint) {
|
||||
self.stack_bounds
|
||||
}
|
||||
|
||||
/// Returns the stack guard for this task, if known.
|
||||
/// Returns the stack guard for this thread, if known.
|
||||
pub fn stack_guard(&self) -> Option<uint> {
|
||||
if self.stack_guard != 0 {
|
||||
Some(self.stack_guard)
|
||||
|
@ -385,9 +385,9 @@ impl Task {
|
|||
}
|
||||
}
|
||||
|
||||
/// Consume this task, flagging it as a candidate for destruction.
|
||||
/// Consume this thread, flagging it as a candidate for destruction.
|
||||
///
|
||||
/// This function is required to be invoked to destroy a task. A task
|
||||
/// This function is required to be invoked to destroy a thread. A thread
|
||||
/// destroyed through a normal drop will abort.
|
||||
pub fn drop(mut self) {
|
||||
self.state = Destroyed;
|
||||
|
@ -396,7 +396,7 @@ impl Task {
|
|||
|
||||
impl Drop for Task {
|
||||
fn drop(&mut self) {
|
||||
rtdebug!("called drop for a task: {}", self as *mut Task as uint);
|
||||
rtdebug!("called drop for a thread: {}", self as *mut Task as uint);
|
||||
rtassert!(self.state != Armed);
|
||||
}
|
||||
}
|
||||
|
@ -414,7 +414,7 @@ impl Iterator<BlockedTask> for BlockedTasks {
|
|||
}
|
||||
|
||||
impl BlockedTask {
|
||||
/// Returns Some if the task was successfully woken; None if already killed.
|
||||
/// Returns Some if the thread was successfully woken; None if already killed.
|
||||
pub fn wake(self) -> Option<Box<Task>> {
|
||||
match self {
|
||||
Owned(task) => Some(task),
|
||||
|
@ -427,7 +427,7 @@ impl BlockedTask {
|
|||
}
|
||||
}
|
||||
|
||||
/// Reawakens this task if ownership is acquired. If finer-grained control
|
||||
/// Reawakens this thread if ownership is acquired. If finer-grained control
|
||||
/// is desired, use `wake` instead.
|
||||
pub fn reawaken(self) {
|
||||
self.wake().map(|t| t.reawaken());
|
||||
|
@ -438,12 +438,12 @@ impl BlockedTask {
|
|||
#[cfg(not(test))] pub fn trash(self) { }
|
||||
#[cfg(test)] pub fn trash(self) { assert!(self.wake().is_none()); }
|
||||
|
||||
/// Create a blocked task, unless the task was already killed.
|
||||
/// Create a blocked thread, unless the thread was already killed.
|
||||
pub fn block(task: Box<Task>) -> BlockedTask {
|
||||
Owned(task)
|
||||
}
|
||||
|
||||
/// Converts one blocked task handle to a list of many handles to the same.
|
||||
/// Converts one blocked thread handle to a list of many handles to the same.
|
||||
pub fn make_selectable(self, num_handles: uint) -> Take<BlockedTasks> {
|
||||
let arc = match self {
|
||||
Owned(task) => {
|
||||
|
@ -543,7 +543,7 @@ mod test {
|
|||
drop(Task::new(None, None));
|
||||
}
|
||||
|
||||
// Task blocking tests
|
||||
// Thread blocking tests
|
||||
|
||||
#[test]
|
||||
fn block_and_wake() {
|
||||
|
|
|
@ -79,7 +79,7 @@ struct Exception {
|
|||
|
||||
pub type Callback = fn(msg: &(Any + Send), file: &'static str, line: uint);
|
||||
|
||||
// Variables used for invoking callbacks when a task starts to unwind.
|
||||
// Variables used for invoking callbacks when a thread starts to unwind.
|
||||
//
|
||||
// For more information, see below.
|
||||
const MAX_CALLBACKS: uint = 16;
|
||||
|
@ -106,14 +106,14 @@ thread_local! { static PANICKING: Cell<bool> = Cell::new(false) }
|
|||
///
|
||||
/// * This is not safe to call in a nested fashion. The unwinding
|
||||
/// interface for Rust is designed to have at most one try/catch block per
|
||||
/// task, not multiple. No runtime checking is currently performed to uphold
|
||||
/// thread, not multiple. No runtime checking is currently performed to uphold
|
||||
/// this invariant, so this function is not safe. A nested try/catch block
|
||||
/// may result in corruption of the outer try/catch block's state, especially
|
||||
/// if this is used within a task itself.
|
||||
/// if this is used within a thread itself.
|
||||
///
|
||||
/// * It is not sound to trigger unwinding while already unwinding. Rust tasks
|
||||
/// * It is not sound to trigger unwinding while already unwinding. Rust threads
|
||||
/// have runtime checks in place to ensure this invariant, but it is not
|
||||
/// guaranteed that a rust task is in place when invoking this function.
|
||||
/// guaranteed that a rust thread is in place when invoking this function.
|
||||
/// Unwinding twice can lead to resource leaks where some destructors are not
|
||||
/// run.
|
||||
pub unsafe fn try<F: FnOnce()>(f: F) -> Result<(), Box<Any + Send>> {
|
||||
|
@ -203,7 +203,7 @@ fn rust_exception_class() -> uw::_Unwind_Exception_Class {
|
|||
// _URC_INSTALL_CONTEXT (i.e. "invoke cleanup code") in cleanup phase.
|
||||
//
|
||||
// This is pretty close to Rust's exception handling approach, except that Rust
|
||||
// does have a single "catch-all" handler at the bottom of each task's stack.
|
||||
// does have a single "catch-all" handler at the bottom of each thread's stack.
|
||||
// So we have two versions of the personality routine:
|
||||
// - rust_eh_personality, used by all cleanup landing pads, which never catches,
|
||||
// so the behavior of __gcc_personality_v0 is perfectly adequate there, and
|
||||
|
@ -570,7 +570,7 @@ pub fn begin_unwind<M: Any + Send>(msg: M, file_line: &(&'static str, uint)) ->
|
|||
// Currently this means that panic!() on OOM will invoke this code path,
|
||||
// but then again we're not really ready for panic on OOM anyway. If
|
||||
// we do start doing this, then we should propagate this allocation to
|
||||
// be performed in the parent of this task instead of the task that's
|
||||
// be performed in the parent of this thread instead of the thread that's
|
||||
// panicking.
|
||||
|
||||
// see below for why we do the `Any` coercion here.
|
||||
|
@ -593,7 +593,7 @@ fn begin_unwind_inner(msg: Box<Any + Send>, file_line: &(&'static str, uint)) ->
|
|||
static INIT: Once = ONCE_INIT;
|
||||
INIT.doit(|| unsafe { register(failure::on_fail); });
|
||||
|
||||
// First, invoke call the user-defined callbacks triggered on task panic.
|
||||
// First, invoke call the user-defined callbacks triggered on thread panic.
|
||||
//
|
||||
// By the time that we see a callback has been registered (by reading
|
||||
// MAX_CALLBACKS), the actual callback itself may have not been stored yet,
|
||||
|
@ -621,7 +621,7 @@ fn begin_unwind_inner(msg: Box<Any + Send>, file_line: &(&'static str, uint)) ->
|
|||
// If a thread panics while it's already unwinding then we
|
||||
// have limited options. Currently our preference is to
|
||||
// just abort. In the future we may consider resuming
|
||||
// unwinding or otherwise exiting the task cleanly.
|
||||
// unwinding or otherwise exiting the thread cleanly.
|
||||
rterrln!("thread panicked while panicking. aborting.");
|
||||
unsafe { intrinsics::abort() }
|
||||
}
|
||||
|
@ -629,10 +629,10 @@ fn begin_unwind_inner(msg: Box<Any + Send>, file_line: &(&'static str, uint)) ->
|
|||
rust_panic(msg);
|
||||
}
|
||||
|
||||
/// Register a callback to be invoked when a task unwinds.
|
||||
/// Register a callback to be invoked when a thread unwinds.
|
||||
///
|
||||
/// This is an unsafe and experimental API which allows for an arbitrary
|
||||
/// callback to be invoked when a task panics. This callback is invoked on both
|
||||
/// callback to be invoked when a thread panics. This callback is invoked on both
|
||||
/// the initial unwinding and a double unwinding if one occurs. Additionally,
|
||||
/// the local `Task` will be in place for the duration of the callback, and
|
||||
/// the callback must ensure that it remains in place once the callback returns.
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
//! Abstraction of a task pool for basic parallelism.
|
||||
//! Abstraction of a thread pool for basic parallelism.
|
||||
|
||||
use core::prelude::*;
|
||||
|
||||
|
@ -45,9 +45,9 @@ impl<'a> Drop for Sentinel<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// A task pool used to execute functions in parallel.
|
||||
/// A thread pool used to execute functions in parallel.
|
||||
///
|
||||
/// Spawns `n` worker tasks and replenishes the pool if any worker tasks
|
||||
/// Spawns `n` worker threads and replenishes the pool if any worker threads
|
||||
/// panic.
|
||||
///
|
||||
/// # Example
|
||||
|
@ -69,34 +69,34 @@ impl<'a> Drop for Sentinel<'a> {
|
|||
/// assert_eq!(rx.iter().take(8u).sum(), 8u);
|
||||
/// ```
|
||||
pub struct TaskPool {
|
||||
// How the taskpool communicates with subtasks.
|
||||
// How the threadpool communicates with subthreads.
|
||||
//
|
||||
// This is the only such Sender, so when it is dropped all subtasks will
|
||||
// This is the only such Sender, so when it is dropped all subthreads will
|
||||
// quit.
|
||||
jobs: Sender<Thunk>
|
||||
}
|
||||
|
||||
impl TaskPool {
|
||||
/// Spawns a new task pool with `tasks` tasks.
|
||||
/// Spawns a new thread pool with `threads` threads.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if `tasks` is 0.
|
||||
pub fn new(tasks: uint) -> TaskPool {
|
||||
assert!(tasks >= 1);
|
||||
/// This function will panic if `threads` is 0.
|
||||
pub fn new(threads: uint) -> TaskPool {
|
||||
assert!(threads >= 1);
|
||||
|
||||
let (tx, rx) = channel::<Thunk>();
|
||||
let rx = Arc::new(Mutex::new(rx));
|
||||
|
||||
// Taskpool tasks.
|
||||
for _ in range(0, tasks) {
|
||||
// Threadpool threads
|
||||
for _ in range(0, threads) {
|
||||
spawn_in_pool(rx.clone());
|
||||
}
|
||||
|
||||
TaskPool { jobs: tx }
|
||||
}
|
||||
|
||||
/// Executes the function `job` on a task in the pool.
|
||||
/// Executes the function `job` on a thread in the pool.
|
||||
pub fn execute<F>(&self, job: F)
|
||||
where F : FnOnce(), F : Send
|
||||
{
|
||||
|
@ -106,7 +106,7 @@ impl TaskPool {
|
|||
|
||||
fn spawn_in_pool(jobs: Arc<Mutex<Receiver<Thunk>>>) {
|
||||
Thread::spawn(move |:| {
|
||||
// Will spawn a new task on panic unless it is cancelled.
|
||||
// Will spawn a new thread on panic unless it is cancelled.
|
||||
let sentinel = Sentinel::new(&jobs);
|
||||
|
||||
loop {
|
||||
|
@ -165,12 +165,12 @@ mod test {
|
|||
|
||||
let pool = TaskPool::new(TEST_TASKS);
|
||||
|
||||
// Panic all the existing tasks.
|
||||
// Panic all the existing threads.
|
||||
for _ in range(0, TEST_TASKS) {
|
||||
pool.execute(move|| -> () { panic!() });
|
||||
}
|
||||
|
||||
// Ensure new tasks were spawned to compensate.
|
||||
// Ensure new threads were spawned to compensate.
|
||||
let (tx, rx) = channel();
|
||||
for _ in range(0, TEST_TASKS) {
|
||||
let tx = tx.clone();
|
||||
|
@ -189,7 +189,7 @@ mod test {
|
|||
let pool = TaskPool::new(TEST_TASKS);
|
||||
let waiter = Arc::new(Barrier::new(TEST_TASKS + 1));
|
||||
|
||||
// Panic all the existing tasks in a bit.
|
||||
// Panic all the existing threads in a bit.
|
||||
for _ in range(0, TEST_TASKS) {
|
||||
let waiter = waiter.clone();
|
||||
pool.execute(move|| {
|
||||
|
|
Loading…
Reference in New Issue