-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Stabilize std::thread #20615
Conversation
r? @pcwalton (rust_highfive has picked a reviewer for you, use r? to override) |
r? @alexcrichton |
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. |
@pythonesque We could always re-introduce a Thanks for the feedback! |
I have the same concerns as @pythonesque. I like this API, but I think we should still have a way to explicitly 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");
} |
I'd suggest reintroducing |
Thanks for the feedback @kballard! Reintroducing as |
Added back |
Are we okay with panicing if thread creation fails? Should |
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 {} |
There was a problem hiding this comment.
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?
1c84946
to
6ee660b
Compare
It's a good question. My and @alexcrichton's instinct here is that this is a relatively niche concern that could be relegated to In any case, these are @alexcrichton Nits addressed. |
@@ -0,0 +1,857 @@ | |||
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT |
There was a problem hiding this comment.
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!
@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> { |
There was a problem hiding this comment.
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
?
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]
@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 |
And conversely, thread creation is comparatively much rarer than memory allocation so the cost of returning a |
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. |
@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 Usually we avoid such duplication (i.e., no 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 |
Seems reasonable to me. |
👍 |
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]
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 thaton destruction joined the thread (unless
detach
was called).The reason to have the division in part is that
Send
will soon notimply
'static
, which means thatscoped
thread creation can take aclosure 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 toprevent this from happening by joining on drop (if you have not
already explicitly
join
ed.) The APIs aroundscoped
arefuture-proofed for the
Send
changes by taking an additional lifetimeparameter. With the current definition of
Send
, this is forced to be'static
, but whenSend
changes these APIs will gain their fullflexibility immediately.
Threads that are
spawn
ed, on the other hand, are detached from thestart and do not yield an RAII guard.
The hope is that, by making
scoped
an explicit opt-in with a verysuggestive 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
andscoped
are marked stable.The migration path is:
becomes
while
becomes
[breaking-change]