Dubrzr
Dubrzr

Reputation: 317

Threads with lambda [&] leading to mystic results

I am currently developing a multi-threaded server (with c++14) and I am facing kind of a strange problem, let me explain:

So here I am just creating nb_threads threads with a lambda expression and putting them in a forward_list:

unsigned nb_threads = 4;
std::forward_list<std::thread> threads;

for (unsigned i = 0; i < nb_threads; ++i)
      threads.emplace_front(std::thread(
            [i, this]()
            {
              std::cout << "Thread " << i + 1 << " launched!" << std::endl;
            }));

This gives me this result on the stdout (which is correct):

Thread 2 launched!
Thread 3 launched!
Thread 4 launched!
Thread 1 launched!

Now let's just change [i, this] by [&] in the lambda expression:

for (unsigned i = 0; i < nb_threads; ++i)
      threads.emplace_front(std::thread(
            [&]()
            {
              std::cout << "Thread " << i + 1 << " launched!" << std::endl;
            }));

This now gives me this result on the stdout (which is really mystic!):

Thread 2 launched!
Thread 1 launched!
Thread 1 launched!
Thread 1 launched!

So my question is What is happening ? Why can't I change for [&] ?

Upvotes: 3

Views: 372

Answers (3)

Felix Glas
Felix Glas

Reputation: 15534

Race Condition

What is happening here when capturing i by reference is a data race. When capturing by reference, each lambda will get a reference to the same memory location as the variable captured by reference.

Basically what is going on is that the main thread is writing to i while the spawned threads are trying to read from the same variable, without synchronization.

Performing reads and writes to the same memory location concurrently without synchronization will cause a data race and exhibit nondeterministic behaviour.

Upvotes: 3

kuroi neko
kuroi neko

Reputation: 8661

This is no more "mystic" than the difference between

void thread_body (int i) {}

and

void thread_body (int &i) {}

In the first case the current value of the argument is passed to the thread and i won't change unless the function itself modifies it.

In the second you pass a reference to some external variable that is likely to change anytime, outside the function's control.

The only mystic thing here is the bloody awful C++ syntax, but since you're writing a multitasking C++14 server, you'll soon get used to it.

Upvotes: 3

David Schwartz
David Schwartz

Reputation: 182827

As the value of i changes, the value of references to i changes with it. Since you have no synchronization, such changes are unpredictable. Here, you clearly want to capture i by value.

Upvotes: 3

Related Questions