6

Scoped threads in the standard library, take 2 by bstrie · Pull Request #3151 ·...

 3 years ago
source link: https://github.com/rust-lang/rfcs/pull/3151
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Conversation

Copy link

Contributor

bstrie commented 4 days ago

edited

The successor to #2647 .

Add scoped threads to the standard library that allow spawning threads that borrow variables from the parent thread.

let var = String::from("foo");

thread::scope(|s| {
    s.spawn(|_| println!("borrowed from thread #1: {}", var));
    s.spawn(|_| println!("borrowed from thread #2: {}", var));
});

Rendered

feeling completely comfortable with including scoped threads in Rust 1.0 so it

was decided they should live in external crates, with the possibility of going

back into the standard library sometime in the future.

Four years have passed since then and the future is now.

BlackHoleFox 4 days ago

Suggested change
Four years have passed since then and the future is now. Six years have passed since then and the future is now.

bstrie 3 days ago

Author

Contributor

"Four years had passed since then and the future was now; two years have passed since the future was now and the future is now past."

Note that as discussed in #2647 (comment), it is possible to offer an API that doesn't take a parameter on the sub-closures:

let var = &String::from("foo");
thread::scope(|s| {
    s.spawn(move || {
        println!("borrowed from thread #1: {}", var);
        s.spawn(|| println!("Another one"));
    });
    s.spawn(|| println!("borrowed from thread #2: {}", var));
});

although it does require (a copy of) s to be captured by move (aside – I wonder if in a future edition the semantics of "closure that captures a Copy (and non-Freeze) value" could be made so that it automatically captures it in an owned fashion rather than borrowed).

I personally believe that this tiny "detail" of having to add move will, in practice, be a very unergonomic hurdle which, unless palliated with move { s } || … style of closures, makes this design, sadly, worse than the classical |s| { … } one.

So whilst I believe that the current design is better, the ||-design deserves to be, at least, mentioned in the Alternatives section, if only for future reference for other similar designs which could benefit from that knowledge.

to everyone.

# Prior art

[prior-art]: #prior-art

yoshuawuyts 7 hours ago

Member

Prior art also exists in Swift's TaskGroup introduced in SE-0304. Instead of working directly on threads, it works with async "tasks" (and is closer to e.g. an async_std::task::Task). But the design still covers a lot of the same space as we do in Rust, and as such I think it's worth to include in the prior art section.

Can this concept be extended to async? Would there be any behavioral or API differences?

# Future possibilities

[future-possibilities]: #future-possibilities

yoshuawuyts 7 hours ago

Member

This should probably reference "async scopes" #2647 (comment).

# Unresolved questions

[unresolved-questions]: #unresolved-questions

Can this concept be extended to async? Would there be any behavioral or API differences?

yoshuawuyts 7 hours ago

Member

I'd like to read more on the reasoning why we're going for "scoped threads" rather than a "scoped thread pool" (see also: #2647 (comment)). I feel like these two concepts are closer to each other than one might intuitively assume, and it's important to cover the relationship between the two.

but they work on a different abstraction level - Rayon spawns tasks rather than

threads. Its API is the same as the one proposed in this RFC.

# Unresolved questions

yoshuawuyts 7 hours ago

edited

Member

I'd like to raise a point on naming here.

The ambiguity of "Scope"

I remember the first time I learned about "scoped threads" and being confused about what they do. I wondered whether it referred to:

  1. A "scope" as in "function scope"
  2. A "scope" as in "telemetry scope"
  3. Some other kind of "scope" specific to concurrent programming I was not yet aware of.

To this day I'm still not entirely clear on its origin. Though it seems likely that when this API was first introduced in 2014 (#461) the name is a reference to Boost C++ Scoped Threads, which roughly seems to use "scope" as an analog for "grouping of".

Alternative Naming

Instead I would prefer we follow Swift's example, using simpler naming, and go with ThreadGroup:

- struct Scope<'env> {}
+ struct ThreadGroup<'env> {}

- fn scope<'env, F, T>(f: F) -> T {}
+ impl ThreadGroup<'env> {
+     fn new<'env, F, T>(f: F) -> T {}
+ }

- struct ScopedJoinHandle<'scope, T> {}
+ struct GroupJoinHandle<'scope, T> {}

The name ThreadGroup implies a type containing threads which work as a group — which is exactly what this API does. Seeing ThreadGroup referenced inside code should also be immediately clear as to what it does. If we contrast the two APIs:

// Immediately clear that this refers to a group of threads.
struct Thing {
    group: ThreadGroup,
}

// `Scope` requires additional context to clarify what it does.
struct Thing {
    scope: Scope,
}

Diggsey 7 hours ago

Contributor

Scope means "lexical scope" (where "function scope" is one kind of lexical scope). They're named "scoped threads" because the API ensures the threads have exited before the scope ends.

A "thread group" is a much weaker concept - simply a set of threads that could be "joined" as one. A thread group need not be scoped.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Assignees

No one assigned

Projects

None yet

Milestone

No milestone

Linked issues

Successfully merging this pull request may close these issues.

None yet

6 participants

Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK