Skip to content

De-stabilize thread::scoped and friends #24385

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/doc/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ safe concurrent programs.
Here's an example of a concurrent Rust program:

```{rust}
# #![feature(scoped)]
use std::thread;

fn main() {
Expand Down Expand Up @@ -421,6 +422,7 @@ problem.
Let's see an example. This Rust code will not compile:

```{rust,ignore}
# #![feature(scoped)]
use std::thread;

fn main() {
Expand Down Expand Up @@ -467,6 +469,7 @@ that our mutation doesn't cause a data race.
Here's what using a Mutex looks like:

```{rust}
# #![feature(scoped)]
use std::thread;
use std::sync::Mutex;

Expand Down Expand Up @@ -527,6 +530,7 @@ As an example, Rust's ownership system is _entirely_ at compile time. The
safety check that makes this an error about moved values:

```{rust,ignore}
# #![feature(scoped)]
use std::thread;

fn main() {
Expand Down
49 changes: 8 additions & 41 deletions src/doc/trpl/concurrency.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,68 +56,35 @@ place!

## Threads

Rust's standard library provides a library for 'threads', which allow you to
Rust's standard library provides a library for threads, which allow you to
run Rust code in parallel. Here's a basic example of using `std::thread`:

```
use std::thread;

fn main() {
thread::scoped(|| {
thread::spawn(|| {
println!("Hello from a thread!");
});
}
```

The `thread::scoped()` method accepts a closure, which is executed in a new
thread. It's called `scoped` because this thread returns a join guard:
The `thread::spawn()` method accepts a closure, which is executed in a
new thread. It returns a handle to the thread, that can be used to
wait for the child thread to finish and extract its result:

```
use std::thread;

fn main() {
let guard = thread::scoped(|| {
println!("Hello from a thread!");
let handle = thread::spawn(|| {
"Hello from a thread!"
});

// guard goes out of scope here
println!("{}", handle.join().unwrap());
}
```

When `guard` goes out of scope, it will block execution until the thread is
finished. If we didn't want this behaviour, we could use `thread::spawn()`:

```
use std::thread;

fn main() {
thread::spawn(|| {
println!("Hello from a thread!");
});

thread::sleep_ms(50);
}
```

We need to `sleep` here because when `main()` ends, it kills all of the
running threads.

[`scoped`](std/thread/struct.Builder.html#method.scoped) has an interesting
type signature:

```text
fn scoped<'a, T, F>(self, f: F) -> JoinGuard<'a, T>
where T: Send + 'a,
F: FnOnce() -> T,
F: Send + 'a
```

Specifically, `F`, the closure that we pass to execute in the new thread. It
has two restrictions: It must be a `FnOnce` from `()` to `T`. Using `FnOnce`
allows the closure to take ownership of any data it mentions from the parent
thread. The other restriction is that `F` must be `Send`. We aren't allowed to
transfer this ownership unless the type thinks that's okay.

Many languages have the ability to execute threads, but it's wildly unsafe.
There are entire books about how to prevent errors that occur from shared
mutable state. Rust helps out with its type system here as well, by preventing
Expand Down
4 changes: 2 additions & 2 deletions src/librustdoc/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,10 @@ struct Output {

pub fn main() {
const STACK_SIZE: usize = 32000000; // 32MB
let res = std::thread::Builder::new().stack_size(STACK_SIZE).scoped(move || {
let res = std::thread::Builder::new().stack_size(STACK_SIZE).spawn(move || {
let s = env::args().collect::<Vec<_>>();
main_args(&s)
}).unwrap().join();
}).unwrap().join().unwrap();
env::set_exit_status(res as i32);
}

Expand Down
55 changes: 41 additions & 14 deletions src/libstd/thread/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,33 @@
//! thread. This means that it can outlive its parent (the thread that spawned
//! it), unless this parent is the main thread.
//!
//! The parent thread can also wait on the completion of the child
//! thread; a call to `spawn` produces a `JoinHandle`, which provides
//! a `join` method for waiting:
//!
//! ```rust
//! use std::thread;
//!
//! let child = thread::spawn(move || {
//! // some work here
//! });
//! // some work here
//! let res = child.join();
//! ```
//!
//! The `join` method returns a `Result` containing `Ok` of the final
//! value produced by the child thread, or `Err` of the value given to
//! a call to `panic!` if the child panicked.
//!
//! ## Scoped threads
//!
//! Often a parent thread uses a child thread to perform some particular task,
//! and at some point must wait for the child to complete before continuing.
//! For this scenario, use the `thread::scoped` function:
//! The `spawn` method does not allow the child and parent threads to
//! share any stack data, since that is not safe in general. However,
//! `scoped` makes it possible to share the parent's stack by forcing
//! a join before any relevant stack frames are popped:
//!
//! ```rust
//! # #![feature(scoped)]
//! use std::thread;
//!
//! let guard = thread::scoped(move || {
Expand Down Expand Up @@ -253,8 +273,8 @@ impl Builder {
/// `io::Result` to capture any failure to create the thread at
/// the OS level.
#[stable(feature = "rust1", since = "1.0.0")]
pub fn spawn<F>(self, f: F) -> io::Result<JoinHandle> where
F: FnOnce(), F: Send + 'static
pub fn spawn<F, T>(self, f: F) -> io::Result<JoinHandle<T>> where
F: FnOnce() -> T, F: Send + 'static, T: Send + 'static
{
self.spawn_inner(Box::new(f)).map(|i| JoinHandle(i))
}
Expand All @@ -274,7 +294,8 @@ impl Builder {
/// Unlike the `scoped` free function, this method yields an
/// `io::Result` to capture any failure to create the thread at
/// the OS level.
#[stable(feature = "rust1", since = "1.0.0")]
#[unstable(feature = "scoped",
reason = "memory unsafe if destructor is avoided, see #24292")]
pub fn scoped<'a, T, F>(self, f: F) -> io::Result<JoinGuard<'a, T>> where
T: Send + 'a, F: FnOnce() -> T, F: Send + 'a
{
Expand Down Expand Up @@ -370,7 +391,9 @@ impl Builder {
/// Panics if the OS fails to create a thread; use `Builder::spawn`
/// to recover from such errors.
#[stable(feature = "rust1", since = "1.0.0")]
pub fn spawn<F>(f: F) -> JoinHandle where F: FnOnce(), F: Send + 'static {
pub fn spawn<F, T>(f: F) -> JoinHandle<T> where
F: FnOnce() -> T, F: Send + 'static, T: Send + 'static
{
Builder::new().spawn(f).unwrap()
}

Expand All @@ -387,7 +410,8 @@ pub fn spawn<F>(f: F) -> JoinHandle where F: FnOnce(), F: Send + 'static {
///
/// Panics if the OS fails to create a thread; use `Builder::scoped`
/// to recover from such errors.
#[stable(feature = "rust1", since = "1.0.0")]
#[unstable(feature = "scoped",
reason = "memory unsafe if destructor is avoided, see #24292")]
pub fn scoped<'a, T, F>(f: F) -> JoinGuard<'a, T> where
T: Send + 'a, F: FnOnce() -> T, F: Send + 'a
{
Expand Down Expand Up @@ -635,9 +659,9 @@ impl<T> JoinInner<T> {
/// handle: the ability to join a child thread is a uniquely-owned
/// permission.
#[stable(feature = "rust1", since = "1.0.0")]
pub struct JoinHandle(JoinInner<()>);
pub struct JoinHandle<T>(JoinInner<T>);

impl JoinHandle {
impl<T> JoinHandle<T> {
/// Extract a handle to the underlying thread
#[stable(feature = "rust1", since = "1.0.0")]
pub fn thread(&self) -> &Thread {
Expand All @@ -649,13 +673,14 @@ impl JoinHandle {
/// If the child thread panics, `Err` is returned with the parameter given
/// to `panic`.
#[stable(feature = "rust1", since = "1.0.0")]
pub fn join(mut self) -> Result<()> {
pub fn join(mut self) -> Result<T> {
self.0.join()
}
}

#[stable(feature = "rust1", since = "1.0.0")]
impl Drop for JoinHandle {
#[unsafe_destructor]
impl<T> Drop for JoinHandle<T> {
fn drop(&mut self) {
if !self.0.joined {
unsafe { imp::detach(self.0.native) }
Expand All @@ -674,7 +699,8 @@ impl Drop for JoinHandle {
/// handle: the ability to join a child thread is a uniquely-owned
/// permission.
#[must_use = "thread will be immediately joined if `JoinGuard` is not used"]
#[stable(feature = "rust1", since = "1.0.0")]
#[unstable(feature = "scoped",
reason = "memory unsafe if destructor is avoided, see #24292")]
pub struct JoinGuard<'a, T: Send + 'a> {
inner: JoinInner<T>,
_marker: PhantomData<&'a T>,
Expand Down Expand Up @@ -706,7 +732,8 @@ impl<'a, T: Send + 'a> JoinGuard<'a, T> {
}

#[unsafe_destructor]
#[stable(feature = "rust1", since = "1.0.0")]
#[unstable(feature = "scoped",
reason = "memory unsafe if destructor is avoided, see #24292")]
impl<'a, T: Send + 'a> Drop for JoinGuard<'a, T> {
fn drop(&mut self) {
if !self.inner.joined {
Expand Down
4 changes: 2 additions & 2 deletions src/test/bench/shootout-binarytrees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,11 @@ fn main() {
let messages = (min_depth..max_depth + 1).step_by(2).map(|depth| {
use std::num::Int;
let iterations = 2.pow((max_depth - depth + min_depth) as u32);
thread::scoped(move || inner(depth, iterations))
thread::spawn(move || inner(depth, iterations))
}).collect::<Vec<_>>();

for message in messages {
println!("{}", message.join());
println!("{}", message.join().unwrap());
}

println!("long lived tree of depth {}\t check: {}",
Expand Down
4 changes: 2 additions & 2 deletions src/test/bench/shootout-fannkuch-redux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,15 +166,15 @@ fn fannkuch(n: i32) -> (i32, i32) {
for (_, j) in (0..N).zip((0..).step_by(k)) {
let max = cmp::min(j+k, perm.max());

futures.push(thread::scoped(move|| {
futures.push(thread::spawn(move|| {
work(perm, j as usize, max as usize)
}))
}

let mut checksum = 0;
let mut maxflips = 0;
for fut in futures {
let (cs, mf) = fut.join();
let (cs, mf) = fut.join().unwrap();
checksum += cs;
maxflips = cmp::max(maxflips, mf);
}
Expand Down
8 changes: 4 additions & 4 deletions src/test/bench/shootout-k-nucleotide.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,17 +307,17 @@ fn main() {

let nb_freqs: Vec<_> = (1..3).map(|i| {
let input = input.clone();
(i, thread::scoped(move|| generate_frequencies(&input, i)))
(i, thread::spawn(move|| generate_frequencies(&input, i)))
}).collect();
let occ_freqs: Vec<_> = OCCURRENCES.iter().map(|&occ| {
let input = input.clone();
thread::scoped(move|| generate_frequencies(&input, occ.len()))
thread::spawn(move|| generate_frequencies(&input, occ.len()))
}).collect();

for (i, freq) in nb_freqs {
print_frequencies(&freq.join(), i);
print_frequencies(&freq.join().unwrap(), i);
}
for (&occ, freq) in OCCURRENCES.iter().zip(occ_freqs.into_iter()) {
print_occurrences(&mut freq.join(), occ);
print_occurrences(&mut freq.join().unwrap(), occ);
}
}
8 changes: 4 additions & 4 deletions src/test/bench/shootout-mandelbrot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ fn mandelbrot<W: old_io::Writer>(w: usize, mut out: W) -> old_io::IoResult<()> {
let mut precalc_i = Vec::with_capacity(h);

let precalc_futures = (0..WORKERS).map(|i| {
thread::scoped(move|| {
thread::spawn(move|| {
let mut rs = Vec::with_capacity(w / WORKERS);
let mut is = Vec::with_capacity(w / WORKERS);

Expand All @@ -108,7 +108,7 @@ fn mandelbrot<W: old_io::Writer>(w: usize, mut out: W) -> old_io::IoResult<()> {
}).collect::<Vec<_>>();

for res in precalc_futures {
let (rs, is) = res.join();
let (rs, is) = res.join().unwrap();
precalc_r.extend(rs.into_iter());
precalc_i.extend(is.into_iter());
}
Expand All @@ -123,7 +123,7 @@ fn mandelbrot<W: old_io::Writer>(w: usize, mut out: W) -> old_io::IoResult<()> {
let vec_init_r = arc_init_r.clone();
let vec_init_i = arc_init_i.clone();

thread::scoped(move|| {
thread::spawn(move|| {
let mut res: Vec<u8> = Vec::with_capacity((chunk_size * w) / 8);
let init_r_slice = vec_init_r;

Expand All @@ -144,7 +144,7 @@ fn mandelbrot<W: old_io::Writer>(w: usize, mut out: W) -> old_io::IoResult<()> {

try!(writeln!(&mut out as &mut Writer, "P4\n{} {}", w, h));
for res in data {
try!(out.write(&res.join()));
try!(out.write(&res.join().unwrap()));
}
out.flush()
}
Expand Down
2 changes: 1 addition & 1 deletion src/test/bench/shootout-reverse-complement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@

// ignore-android see #10393 #13206

#![feature(unboxed_closures, libc, old_io, collections, io, core)]
#![feature(unboxed_closures, libc, old_io, collections, io, core, scoped)]

extern crate libc;

Expand Down
2 changes: 1 addition & 1 deletion src/test/bench/shootout-spectralnorm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
// no-pretty-expanded FIXME #15189

#![allow(non_snake_case)]
#![feature(unboxed_closures, core, os)]
#![feature(unboxed_closures, core, os, scoped)]

use std::iter::repeat;
use std::thread;
Expand Down
4 changes: 2 additions & 2 deletions src/test/run-fail/panic-task-name-owned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
use std::thread::Builder;

fn main() {
let r: () = Builder::new().name("owned name".to_string()).scoped(move|| {
let r: () = Builder::new().name("owned name".to_string()).spawn(move|| {
panic!("test");
()
}).unwrap().join();
}).unwrap().join().unwrap();
panic!();
}
2 changes: 1 addition & 1 deletion src/test/run-fail/rt-set-exit-status-panic2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ fn r(x:isize) -> r {

fn main() {
error!("whatever");
let _t = thread::scoped(move|| {
let _t = thread::spawn(move|| {
let _i = r(5);
});
panic!();
Expand Down
2 changes: 1 addition & 1 deletion src/test/run-pass/atomic-print.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ fn main(){
if env::args().count() == 2 {
let barrier = sync::Arc::new(sync::Barrier::new(2));
let tbarrier = barrier.clone();
let t = thread::scoped(||{
let t = thread::spawn(move || {
tbarrier.wait();
do_print(1);
});
Expand Down
Loading