I'm new to threading in C++, and I'm trying to get a clear picture about how memory is shared/not shared between threads. I'm using std::thread
with C++11.
From what I've read on other SO questions, stack memory is owned by only one thread and heap memory is shared between threads. So from what I think I understand about the stack vs. the heap, the following should be true:
#include <thread>
using namespace std;
class Obj {
public:
int x;
Obj(){x = 0;}
};
int main() {
Obj stackObj;
Obj *heapObj = new Obj();
thread t([&]{
stackObj.x++;
heapObj->x++;
});
t.join();
assert(heapObj->x == 1);
assert(stackObj.x == 0);
}
forgive me if I screwed up a bunch of stuff, lambda syntax is very new to me. But hopefully what I'm trying to do is coherent. Would this perform as I expect? And if not, what am I misunderstanding?
I agree with James McNellis that
heapObj->x
andstackObj.x
will be1
.Furthermore, this code only works because you
join
immediately after spawning the thread. If you started the thread and then did more work while it runs, an exception could unwind the stack and suddenly the new thread'sstackObj
is invalid. That is why sharing stack memory between threads is a bad idea even if it's technically possible.Memory is memory. An object in C++ occupies some location in memory; that location may be on a stack or on the heap, or it may have been statically allocated. It doesn't matter where the object is located: any thread that has a reference or pointer to the object may access the object. If two threads have a reference or a pointer to the object, then both threads may access it.
In your program, you create a worker thread (by constructing a
std::thread
) that executes the lambda expression you provide it. Because you capture bothstackObj
andheapObj
by reference (using the[&]
capture default), that lambda has references to both of those objects.Those objects are both located on the main thread's stack (note that
heapObj
is a pointer-type object that is located on the main thread's stack and points to a dynamically allocated object that is located on the heap). No copies of these objects are made; rather, your lambda expression has references to the objects. It modifies thestackObj
directly and modifies the object pointed to byheapObj
indirectly.After the main thread joins with the worker thread, both
heapObj->x
andstackObj.x
have a value of1
.If you had used the value capture default (
[=]
), your lambda expression would have copied bothstackObj
andheapObj
. The expressionstackObj.x++
in the lambda expression would increment the copy, and thestackObj
that you declare inmain()
would be left unchanged.If you capture the
heapObj
by value, only the pointer itself is copied, so while a copy of the pointer is used, it still points to the same dynamically allocated object. The expressionheapObj->x++
would dereference that pointer, yielding theObj
you created vianew Obj()
, and increment its value. You would then observe at the end ofmain()
thatheapObj->x
has been incremented.(Note that in order to modify an object captured by value, the lambda expression must be declared
mutable
.)