Thread Stack
I was a little confused on how thread stacks work. Like I get that each thread has its own Stack on creation, but how do passing variables work and stuff?
I ran into this while working on Rust for my ECE459 assignment:
- In Rust,
std::thread::spawntakes a closure that must be'static(and usuallySend) - The thread cannot safely hold a reference to a local variable on
main’s stack, becausemaincould return (or just move on) while the thread is still running. - So captured variables must be owned by the closure (moved in), or they must be references to something that is guaranteed to live long enough (rarely a local stack variable).
What about in C++?
In C++, we can pass references/pointers to stack locals, but it can be undefined behavior.
To illustrate this, consider the follwing example:
{
int x = 5;
std::thread t([&]{ use(x); }); // captures by reference
t.detach(); // thread might run after main continues/returns
}- Since the thread
tis running in detached mode, there is a risk thatxgoes out of scope whiletuses it => undefined behavior
But how can
xgo out of scope if the thread stack is created in the same stack as the the variablex?Isn’t the thread stack terminated when the
std::threadobject goes out of scope?
- Not if you use
t.detach()
You see, std::thread is just a handle to an OS thread. Destroying the handle doesn’t “kill” the thread.
In C++, the behavior is like this
{
std::thread t(work);
} // if still joinable here => std::terminate()If a std::thread object is destroyed while it’s still joinable, the program calls std::terminate().
We do one of these before the std::thread object goes out of scope:
t.join()(waits for the thread to finish)t.detach()(lets it run independently)
In Rust
In Rust, we want to prevent this dangerous behavior, which is why the compiler just prevents this from ever being allowed.