Search code examples
c++multithreadingpointerslambdamember-functions

Why does calling a member member function in a separate thread result in non-deterministic behaviour?


I broke down my problem to something that is reproducible. In the following code we have a class printer. printer::run will print a the id_ of that printer. printer::spawn will get you a thread with a new printer.

In main I am spawning 10 printers. The created threads will be joined at the end of main. My expectation would have been that we get the numbers from 0 to 9 in some order every time we run this (this is the "deterministic behavior" from the title). This is not the case. We get other numbers sometimes. Why is that?

#include <thread>
#include <iostream>
#include <vector>

class printer
{
public:
    printer(int i) : i_(i){};
    void run()
    {
        std::cout << i_ << '\n';
    }
    std::shared_ptr<std::thread> spawn()
    {
        return std::make_shared<std::thread>([=]() { run(); });
    }

private:
    int i_;
};

int main()
{
    std::vector<std::shared_ptr<std::thread>> vec;
    for (int i = 0; i < 10; ++i)
    {
        printer p{i};
        vec.push_back(p.spawn());
    }
    for (auto a : vec)
    {
        a->join();
    }
    return 0;
}

Some thoughts about my expectation:

I would have guessed that we copy the state of the current printer when calling spawn, because we capture the state by value (lambda), not by reference.

Even if we would capture by reference, we still copy the state to the new thread.

And even if that is not happening, each thread has its own printer. What am I missing?

Some example outputs:

(I put all the numbers in one line for brevity)

1, 3, 4, 2, 5, 6, 7, 8, 9, -116618192
1513168144, 1513168144, 1513168144, 1513168144, 1513168144, 1513168144, 1513168144, 1513168144, 1513168144, 1513168144
1, 2, 33, , 6, 6, 7, 9, 9, 1344437296
1, 43, 2, 6, , 6, 7, 8, 9, -1194894256

Some outputs do not have all 10 numbers...


Solution

  • This is because you create and destroy a printer object within the loop that creates your threads. When the loop enters the next iteration, the printer created in the previous iteration has been destroyed, and the thread that you created to reference it will reference this destroyed object. This is Undefined Behavior.

    Essentially all your thread will be using the same printer object. At some point the memory it occupies gets overwritten when it is used to hold other data.

    You'll probably want to create an array of printers, so that you can pass a different printer to each thread.