Automatic thread detaching


#1

Context

The spawn function returns a JoinGuard object, which will automatically join the thread when dropped. This means that the next code snippet will spawn a new thread with expensive_fn and will join it before doing the rest (so it will wait until expensive_fn returns before executing cheap_fn).

fn main() {
    Thread::spawn(expensive_fn);
    cheap_fn();
}

Discussion

I think this behavior can be surprising. Coming from C#, the natural thing to me seems that threads are detached unless you state otherwise. Rust seems to do the opposite. Why was this approach chosen instead of automatic detaching? Does any other language take this approach?


#2

I’m not sure why this change was made, but just noting that it was introduced recently.

Before the merge of pull request #19654, threads were automatically detached when spawned.


#3

From the PR:

One important change here is that a Rust program ends when its main thread does, following most threading models. On the other hand, threads will often be created with an RAII-style join handle that will re-institute blocking semantics naturally (and with finer control).

Personally, I like this change because it forces the programmer to manage threads explicitly.


#4

AFAICS, you can call detach() to detach it.

use std::thread::Thread;
use std::io::timer::sleep;
use std::time::duration::Duration;

fn main() {
    Thread::spawn(move || {
        sleep(Duration::days(2));
    }).detach();
    println!("Hello world!");
}

Documentation http://doc.rust-lang.org/std/thread/ Playpen http://is.gd/pyVR1H (EDIT: Called spawn directly)


#5

FWIW, the return value is marked #[must_use], so the compiler does give you a hint that ignoring it is probably not the right thing to do:

fn main() {
    std::thread::Thread::spawn(move || {});
}
<anon>:2:5: 2:44 warning: unused result which must be used, #[warn(unused_must_use)] on by default
<anon>:2     std::thread::Thread::spawn(move || {});
             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

I guess the type could use #[must_use = "..."] to include a custom message in that output and so be more helpful.

(That said, I don’t know the motivation for join-by-default.)


#7

This is somewhat of a work in progress. In the long run the idea is to be able to spawn a thread with a function that closes over stack data (that is, is not 'static), which in turn depends on changing the definition of Send. For such a closure, you cannot detach.

The current design is in a kind of weird midpoint, though. I think what we ultimately want (and what I originally wrote) is to have two spawn functions, one that is immediately detached, and another that returns a JoinGuard and is more flexible with closures. The JoinGuard should probably contain a destructor bomb (i.e. should panic if the destructor is invoked) to ensure that joins are explicit. (Otherwise a missed join will likely result in a hard-to-debug hang.)

These changes are likely to land in the very near future.


#8

Here is a PR making the changes I was outlining.