Skip to content
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

Stabilize std::thread #20615

Merged
merged 2 commits into from
Jan 7, 2015
Merged

Stabilize std::thread #20615

merged 2 commits into from
Jan 7, 2015

Conversation

aturon
Copy link
Member

@aturon aturon commented Jan 6, 2015

This commit takes a first pass at stabilizing std::thread:

  • It removes the detach method in favor of two constructors -- spawn
    for detached threads, scoped for "scoped" (i.e., must-join)
    threads. This addresses some of the surprise/frustrating debug
    sessions with the previous API, in which spawn produced a guard that
    on destruction joined the thread (unless detach was called).

    The reason to have the division in part is that Send will soon not
    imply 'static, which means that scoped thread creation can take a
    closure over shared stack data of the parent thread. On the other
    hand, this means that the parent must not pop the relevant stack
    frames while the child thread is running. The JoinGuard is used to
    prevent this from happening by joining on drop (if you have not
    already explicitly joined.) The APIs around scoped are
    future-proofed for the Send changes by taking an additional lifetime
    parameter. With the current definition of Send, this is forced to be
    'static, but when Send changes these APIs will gain their full
    flexibility immediately.

    Threads that are spawned, on the other hand, are detached from the
    start and do not yield an RAII guard.

    The hope is that, by making scoped an explicit opt-in with a very
    suggestive name, it will be drastically less likely to be caught by a
    surprising deadlock due to an implicit join at the end of a scope.

  • The module itself is marked stable.

  • Existing methods other than spawn and scoped are marked stable.

The migration path is:

Thread::spawn(f).detached()

becomes

Thread::spawn(f)

while

let res = Thread::spawn(f);
res.join()

becomes

let res = Thread::scoped(f);
res.join()

[breaking-change]

@rust-highfive
Copy link
Collaborator

r? @pcwalton

(rust_highfive has picked a reviewer for you, use r? to override)

@aturon
Copy link
Member Author

aturon commented Jan 6, 2015

r? @alexcrichton
cc @pythonesque

@pythonesque
Copy link
Contributor

This API makes sense, and it's good to see that this usecase will be supported in the standard library.

The one concern I have is that with this API, you have no choice but to detach the thread immediately, which is a limitation that didn't previously exist. But I can't immediately think of a reason not to detach immediately if you were going to detach at all, so maybe this is acceptable.

@aturon
Copy link
Member Author

aturon commented Jan 6, 2015

@pythonesque We could always re-introduce a detach method on JoinGuard<'static, T>. I wanted to avoid that for now, to keep our options maximally open.

Thanks for the feedback!

@lilyball
Copy link
Contributor

lilyball commented Jan 6, 2015

I have the same concerns as @pythonesque. I like this API, but I think we should still have a way to explicitly detach() when necessary, because I could imagine edge cases where a program can't decide if it wants to detach until the the thread has already started work.

Here's an example of a toy program that requires this ability:

use std::thread::Thread;
use std::sync::mpsc::channel;
use std::rand::{self, Rng};
use std::io::timer::sleep;
use std::time::duration::Duration;

fn run_thread() {
    let (tx, rx) = channel();
    println!("running thread");
    let guard = Thread::spawn(move || {
        tx.send(rand::thread_rng().gen_range(0f32, 1f32)).unwrap();
        println!("thread sleeping");
        sleep(Duration::seconds(2));
        println!("thread finished sleeping");
    });
    if rx.recv().unwrap() < 0.5 {
        println!("detaching");
        guard.detach()
    } else {
        println!("joining")
    }
}

fn main() {
    run_thread();
    println!("exiting main thread");
}

@lilyball
Copy link
Contributor

lilyball commented Jan 6, 2015

I wanted to avoid that for now, to keep our options maximally open.

I'd suggest reintroducing detach() but leave it marked #[unstable] or #[experimental], if you don't want to commit to keeping it.

@aturon
Copy link
Member Author

aturon commented Jan 6, 2015

Thanks for the feedback @kballard! Reintroducing as #[experimental] seems harmless enough; I'll update.

@aturon
Copy link
Member Author

aturon commented Jan 6, 2015

Added back detach.

@sfackler
Copy link
Member

sfackler commented Jan 6, 2015

Are we okay with panicing if thread creation fails? Should spawn and detach return Results?

@pythonesque
Copy link
Contributor

Ah yeah, that would be useful as well.

/// A handle to a thread.
pub struct Thread {
inner: Arc<Inner>,
}

#[stable]
unsafe impl Sync for Thread {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This impl shouldn't be necessary, right? The Arc implies as much?

@aturon aturon force-pushed the stab-2-thread branch 2 times, most recently from 1c84946 to 6ee660b Compare January 6, 2015 16:51
@aturon
Copy link
Member Author

aturon commented Jan 6, 2015

@sfackler

Are we okay with panicing if thread creation fails? Should spawn and detach return Results?

It's a good question. My and @alexcrichton's instinct here is that this is a relatively niche concern that could be relegated to std::os::thread (or something like that, details still being worked out) -- a lower-level, cross-platform thread API.

In any case, these are #[unstable] for now so we can revisit during alpha.

@alexcrichton Nits addressed.

@@ -0,0 +1,857 @@
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's back from the dead!

@thestinger
Copy link
Contributor

@sfackler: It's not much different from memory allocation failing. It needs to be handled in robust software but Rust's libraries are not usable for that anyway.

pub struct JoinGuard<T> {
#[must_use]
#[unstable = "may change with specifics of new Send semantics"]
pub struct JoinGuard<'a, T> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this also have T: 'a ?

@alexcrichton
Copy link
Member

This is a pretty conservative step, so r=me with a few nits, we've still got some room to tweak interfaces here and there as necessary.

This commit takes a first pass at stabilizing `std::thread`:

* It removes the `detach` method in favor of two constructors -- `spawn`
  for detached threads, `scoped` for "scoped" (i.e., must-join)
  threads. This addresses some of the surprise/frustrating debug
  sessions with the previous API, in which `spawn` produced a guard that
  on destruction joined the thread (unless `detach` was called).

  The reason to have the division in part is that `Send` will soon not
  imply `'static`, which means that `scoped` thread creation can take a
  closure over *shared stack data* of the parent thread. On the other
  hand, this means that the parent must not pop the relevant stack
  frames while the child thread is running. The `JoinGuard` is used to
  prevent this from happening by joining on drop (if you have not
  already explicitly `join`ed.) The APIs around `scoped` are
  future-proofed for the `Send` changes by taking an additional lifetime
  parameter. With the current definition of `Send`, this is forced to be
  `'static`, but when `Send` changes these APIs will gain their full
  flexibility immediately.

  Threads that are `spawn`ed, on the other hand, are detached from the
  start and do not yield an RAII guard.

  The hope is that, by making `scoped` an explicit opt-in with a very
  suggestive name, it will be drastically less likely to be caught by a
  surprising deadlock due to an implicit join at the end of a scope.

* The module itself is marked stable.

* Existing methods other than `spawn` and `scoped` are marked stable.

The migration path is:

```rust
Thread::spawn(f).detached()
```

becomes

```rust
Thread::spawn(f)
```

while

```rust
let res = Thread::spawn(f);
res.join()
```

becomes

```rust
let res = Thread::scoped(f);
res.join()
```

[breaking-change]
@pythonesque
Copy link
Contributor

@thestinger Failing to initialize a new thread happens a lot more often than failing to allocate memory (at least in my experience). Though I guess in some sense the problem fixes itself by killing the thread :P

@sfackler
Copy link
Member

sfackler commented Jan 6, 2015

And conversely, thread creation is comparatively much rarer than memory allocation so the cost of returning a Result from thread creation is far lower than returning one from any memory allocation location.

@pythonesque
Copy link
Contributor

On the implementation side, would it be better to use Option instead of having a "joined" flag? That should save a word in the struct size.

@aturon
Copy link
Member Author

aturon commented Jan 6, 2015

@pythonesque @sfackler @alexcrichton I talked with some of you all about this on IRC, but: I think a good middle ground here is for the spawn and scoped methods on the builder to return Results, but on Thread (where they are meant as conveniences) to panic in these situations.

Usually we avoid such duplication (i.e., no spawn and spawn_result variants), but here the API is already duplicated for convenience; the builder is a kind of power tool. This seems to strike a good balance between making the common case easy and providing the tools for extra robustness if you want them.

All that said, I'd like to move forward with this PR as-is since there's a limited window to land these changes before alpha. Since the relevant bits of the API are #[unstable], we can revisit shortly after the alpha release.

@pythonesque
Copy link
Contributor

Seems reasonable to me.

@sfackler
Copy link
Member

sfackler commented Jan 6, 2015

👍

alexcrichton added a commit to alexcrichton/rust that referenced this pull request Jan 7, 2015
This commit takes a first pass at stabilizing `std::thread`:

* It removes the `detach` method in favor of two constructors -- `spawn`
  for detached threads, `scoped` for "scoped" (i.e., must-join)
  threads. This addresses some of the surprise/frustrating debug
  sessions with the previous API, in which `spawn` produced a guard that
  on destruction joined the thread (unless `detach` was called).

  The reason to have the division in part is that `Send` will soon not
  imply `'static`, which means that `scoped` thread creation can take a
  closure over *shared stack data* of the parent thread. On the other
  hand, this means that the parent must not pop the relevant stack
  frames while the child thread is running. The `JoinGuard` is used to
  prevent this from happening by joining on drop (if you have not
  already explicitly `join`ed.) The APIs around `scoped` are
  future-proofed for the `Send` changes by taking an additional lifetime
  parameter. With the current definition of `Send`, this is forced to be
  `'static`, but when `Send` changes these APIs will gain their full
  flexibility immediately.

  Threads that are `spawn`ed, on the other hand, are detached from the
  start and do not yield an RAII guard.

  The hope is that, by making `scoped` an explicit opt-in with a very
  suggestive name, it will be drastically less likely to be caught by a
  surprising deadlock due to an implicit join at the end of a scope.

* The module itself is marked stable.

* Existing methods other than `spawn` and `scoped` are marked stable.

The migration path is:

```rust
Thread::spawn(f).detached()
```

becomes

```rust
Thread::spawn(f)
```

while

```rust
let res = Thread::spawn(f);
res.join()
```

becomes

```rust
let res = Thread::scoped(f);
res.join()
```

[breaking-change]
@alexcrichton alexcrichton merged commit caca9b2 into rust-lang:master Jan 7, 2015
alkor added a commit to alkor/rust-http that referenced this pull request Jan 11, 2015
@aturon aturon mentioned this pull request Feb 18, 2015
38 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants