Call Lambda in second thread
-
segunda-feira, 12 de março de 2012 21:35
Hi all,
yesterday I got an idea for a small helper in my unit tests. I just wanted to call a lambda in a second thread. So I implemented a simple wrapper accepting a lambda, creating a thread and calling the lambda in the new thread.
First everything looks great, but when I access simple local data like an int, I get an access violation. An instance of a class "sometimes work" but not in all cases.
Here is my code:
#include "stdafx.h" #include <Windows.h> #include <assert.h> template<typename Func> DWORD __stdcall __CallThreadProc(void* _func) { Func* func = (Func*)_func; (*func)(); return 0; } template<typename Func> HANDLE RunAsync(Func func) { void* pFunc = (void*)&func; auto hThread = ::CreateThread(NULL, 0, __CallThreadProc<Func>, pFunc, 0, NULL); if (NULL == hThread) { auto result = HRESULT_FROM_WIN32(::GetLastError()); assert(false); } return hThread; } template<typename Func> HANDLE Run(Func func) { void* pFunc = (void*)&func; __CallThreadProc<Func>(pFunc); return 0; } class A { public: void DoNothing() { } }; int main(void) { int i(0); A a; a.DoNothing(); auto thread = RunAsync([&] () { a.DoNothing(); i+=2; // error }); WaitForSingleObject(thread, INFINITE); ::CloseHandle(thread); assert(i == 2); return 0; }
I tried VS2010 and VS2011 beta. After binging a bit I just found similar examples with boost threads and boost lambdas that supposed to work.
Any help is much appreciated.
"It's time to kick ass and chew bubble gum... and I'm all outta gum." - Duke Nukem
- Editado Do Django segunda-feira, 12 de março de 2012 21:41
Todas as Respostas
-
segunda-feira, 12 de março de 2012 21:49
On 3/12/2012 5:35 PM, Do Django wrote:
yesterday I got an idea for a small helper in my unit tests. I just
wanted to call a lambda in a second thread. So I implemented a simple
wrapper accepting a lambda, creating a thread and calling the lambda
in the new thread.
First everything looks great, but when I access simple local data
like an int, I get an access violation. An instance of a class
"sometimes work" but not in all cases.
template<typename Func>
HANDLE RunAsync(Func func)
{
void* pFunc = (void*)&func;
auto hThread = ::CreateThread(NULL, 0, __CallThreadProc<Func>, pFunc, 0, NULL);func is a local variable in RunAsync. RunAsync may very well return, and destroy the variable, before the thread completes execution (possibly even before it starts execution). Within the thread proc, pFunc may become a dangling pointer.
Igor Tandetnik
- Marcado como Resposta Do Django segunda-feira, 12 de março de 2012 21:56
-
segunda-feira, 12 de março de 2012 21:57Thanks a lot, I reviewed the code again and again but did not see the mistake. Using a reference works fine to avoid the dangling pointer.
"It's time to kick ass and chew bubble gum... and I'm all outta gum." - Duke Nukem
-
segunda-feira, 12 de março de 2012 22:04
On 3/12/2012 5:57 PM, Do Django wrote:
Thanks a lot, I reviewed the code again and again but did not see the mistake. Using a reference works fine to avoid the dangling pointer.
It just masks the problem. In main(), the lambda is a temporary that is destroyed at the end of the full expression containing RunAsync() call - which may still happen before the thread completes. If it now works for you, it's only because the timing, or stack access pattern, has changed slightly, and it's just luck that nothing vital gets overwritten at the wrong moment.
Igor Tandetnik
-
terça-feira, 13 de março de 2012 15:21
Hi Igor,
you're right again, thanks for your advice. In my real code RunAsync returns an object containing a copy of func and the thread handle and now I use the pointer to the member instead of the parameter. The returned object is used to wait for completion, I need this to test some locking mechanism of shared objects.
I would like to learn more about the stack access of vc++. Can you give me some resources in the net to read more about that?
regards,
do"It's time to kick ass and chew bubble gum... and I'm all outta gum." - Duke Nukem
-
terça-feira, 13 de março de 2012 15:44
-
terça-feira, 13 de março de 2012 15:51
Using the func as reference works and the object is alive and accessible in the main function even after RunAsnyc has finished, but why?It just masks the problem. In main(), the lambda is a temporary that is destroyed at the end of the full expression containing RunAsync() call - which may still happen before the thread completes. If it now works for you, it's only because the timing, or stack access pattern, has changed slightly, and it's just luck that nothing vital gets overwritten at the wrong moment.
Igor Tandetnik
"It's time to kick ass and chew bubble gum... and I'm all outta gum." - Duke Nukem
-
terça-feira, 13 de março de 2012 16:30
On 3/13/2012 11:51 AM, Do Django wrote:
It just masks the problem. In main(), the lambda is a temporary
that is destroyed at the end of the full expression containing
RunAsync() call - which may still happen before the thread
completes.*If it now works for you, it's only because the timing,
or stack access pattern, has changed slightly*, and it's just luck
that nothing vital gets overwritten at the wrong moment.Using the func as reference works and the object is alive and accessible in the main function even after RunAsnyc has finished, but why?
In most C and C++ implementations, including that of Visual Studio, local variables and temporaries are given space on the stack. When the execution enters the body of function f(), the stack pointer is adjusted to reserve enough space for that function's local variables. If that function calls another function g(), the stack pointer is adjusted further, to accomodate local variables of g().
When g() returns, the stack pointer is restored back to where it was before this latest call, effectively freeing the space allocated for g()'s local variables and making it available for future use. Note that the compiler doesn't bother to actually overwrite this space - it may still contain whatever bytes were last written to it. This may make it appear that something like this "works":
// Bad code, don't try it at home int* g() { int x = 42; return &x; } int* p = g(); int y = *p;When the function returns, variable x is destroyed, and p is a dangling pointer. But chances are high that the memory that x used to occupy still contains the bytes comprising the integer 42, and y is assigned a value 42. The code is still illegal, only working by accident. If you change it to
int* p = g();
h();
int y = *p;then it's likely that the space that used to be occupied by x will be reused for and overwritten by h()'s local variables, and y will end up with some random garbage. The bug was there all along - adding an extra function call simply exposed the problem.
You have a similar case, where RunAsync passes a pointer to its local variable to the thread proc. Then RunAsync returns, freeing this memory. Then the calling function (main() ) calls another function (WaitForSingleObject) which reuses the now-free stack space for its own needs, overwriting the memory that used to be occupied by the lambda.When you change RunAsync to take a reference, now thread proc gets a pointer to a temporary created in main(), rather than a local variable created in RunAsync. The temporary is, technically, destroyed at the end of the statement where it's created. But a lambda object doesn't own any resources, and its destructor does nothing. The memory it used to occupy still contains the same bytes it did while the object was still alive. This memory sits on the stack, in the chunk that was reserved when main() was called, and it just happens not to be overwritten by WaitForSingleObject's local variables.
The code is still illegal, but it would be more difficult to construct a case that exposes the problem. VC implementation tends to reserve space for all locals and temporaries used by the function up front, so memory left behind by an already-destroyed temporary tends to remain unused and maintain its old values until the function returns. Other implementations may behave differently; if I recall correctly, GCC has an optimization option to reuse space allocated for locals and temporaries, if it can prove that their lifetimes don't overlap.
One way to fix your code would go like this:
auto lambda = [&]() {...}; auto thread = RunAsync(lambda); // RunAsync should take its parameter by reference. WaitForSingleObject(thread, INFINITE);Here, there's no doubt that lambda remains alive throughout the thread's execution.
Igor Tandetnik
-
terça-feira, 13 de março de 2012 21:56
Hi Igor,
thanks again for pointing this out, I see the problem and it is very clear. But what is not really clear is if the following code is legal:
int DoSomething() { int j = 0; int k = 5; int n = j + k; return n; } template <typename Proc> Proc CallAndReturnLambda(Proc proc) { proc(); return proc; } int main(void) { int i = 0; auto procCopy = CallAndReturnLambda([&] () { i+=2; }); assert(i == 2); std::cout << "i = " << i << std::endl; int n = DoSomething(); procCopy(); assert(i == 4); std::cout << "i = " << i << std::endl; return 0; }
procCopy is a copy of the lambda, the temporary is destroyed and as you explain, chances are high that memory still contains the bytes as before. But now there is a copy. Is it legal to copy a lambda? What happens in the copy constructor?
"It's time to kick ass and chew bubble gum... and I'm all outta gum." - Duke Nukem
-
terça-feira, 13 de março de 2012 22:10
On 3/13/2012 5:56 PM, Do Django wrote:
thanks again for pointing this out, I see the problem and it is very clear. But what is not really clear is if the following code is legal:
Looks OK to me.
procCopy is a copy of the lambda, the temporary is destroyed and as you explain, chances are high that memory still contains the bytes as before.
It doesn't matter. The copy was taken while the temporary was still happily alive. procCopy occupies its own storage, independent from that of the temporary.
But now there is a copy. Is it legal to copy a lambda?
Yes.
What happens in the copy constructor?
C++11 5.1.2p19 The closure type associated with a lambda-expression... has an implicitly-declared copy constructor ... [Note: The copy ... constructor is implicitly defined in the same way as any other implicitly declared copy ... constructor would be implicitly defined. —end note ]
The implicitly-declared copy constructor would perform a memberwise copy.
Igor Tandetnik
-
quinta-feira, 15 de março de 2012 20:01
Hi Igor,
thanks again. Next time I will look at the docs before asking ;)
RunAsync now returns an object containing a copy of the lambda, methods to wait and a condition variable for syncing. The desctructor will cause my test cases to fail if the thread is still running.
regards and thank you,
do
"It's time to kick ass and chew bubble gum... and I'm all outta gum." - Duke Nukem

