0

I am working on a project in which I am using threads. The threads are launched with std::async and I ran into some things with member variables in a class that I do not understand. In the code below there are four different implementations of the thread launching function "start_thread_n". The four different functions give somewhat different results when executed and the output I get when running them is shown in the comment before each of the functions. The difference between the functions is in the call std::async(std::launch::async, &ThreadTest::run, &h) and specifically the param "h". What I would like to get is what start_thread_3 delivers (and _4). The difference in behaviour is what the variable m_a contains at different places in the execution of the code, as can be seen in the print outs to the screen (in the comments before each function).

Can you hint on why I get the different results? I run on Linux with C++14.

Many thanks

#include <future>
#include <thread>
#include <vector>
#include <signal.h>

sig_atomic_t g_stop(false);
std::vector<std::future<void>> my_futures;
std::future<void> g_thread;

class ThreadTest
{
public:
        ThreadTest();
        void run();
private:
        unsigned m_a;
};

ThreadTest::ThreadTest()
{
        m_a = 1;
        std::cout << m_a << std::endl;
}

void ThreadTest::run()
{
        std::cout << "Thread started" << std::endl;
        while (not g_stop) {
                std::cout << "." << std::flush;
                std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
        std::cout << std::endl;
        std::cout << m_a << std::endl;
        std::cout << "Thread stopped" << std::endl;
}

void start_thread_1();
void start_thread_2();
void start_thread_3();
ThreadTest start_thread_4();
void stop_threads();

int main()
{
        start_thread_1();
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        g_stop = true;
        stop_threads();
}

/*
 *  > ./thread_test
 * 1
 * Thread started
 * ..........
 * 0
 * Thread stopped
 */
void start_thread_1()
{
        ThreadTest h;
        g_thread = std::async(std::launch::async, &ThreadTest::run, &h);
        my_futures.push_back(std::move(g_thread));
}

/*
 * > ./thread_test
 * Thread started
 * ..........
 * fish: “./thread_test” terminated by signal SIGSEGV (Address boundary error)
 */
void start_thread_2()
{
        std::shared_ptr<ThreadTest> h;
        g_thread = std::async(std::launch::async, &ThreadTest::run, h);
        my_futures.push_back(std::move(g_thread));

}

/*
 *  > ./thread_test
 * 1
 * Thread started
 * ..........
 * 1
 * Thread stopped
 */
void start_thread_3()
{
        ThreadTest h;
        g_thread = std::async(std::launch::async, &ThreadTest::run, h);
        my_futures.push_back(std::move(g_thread));
}

/*
 *  > ./thread_test
 * 1
 * Thread started
 * ..........
 * 1
 * Thread stopped
 */
ThreadTest start_thread_4()
{
        ThreadTest h;
        g_thread = std::async(std::launch::async, &ThreadTest::run, h);
        my_futures.push_back(std::move(g_thread));
        return h;
}

void stop_threads()
{
        size_t m = my_futures.size();
        for(size_t n = 0; n < m; n++) {
                auto e = std::move(my_futures.back());
                e.get();
                my_futures.pop_back();
        }
}```

Mats
  • 69
  • 1
  • 9
  • If I’m not entirely mistaken, you’re giving a reference to a local object which goes out of scope in 1 and 2, which of course will not be ok. But you didn’t explain what exactly is the difference between their results. – Sami Kuhmonen Jun 11 '20 at 12:23
  • 1
    What do you think happens to `start_thread_1`'s `h`, when `start_thread_1` returns after starting a new execution thread running a method of this object? What happens with any local object declared in a function when it returns? It gets destroyed, of course. What do you expect to happen with an execution thread running a method of a destroyed object? – Sam Varshavchik Jun 11 '20 at 12:23
  • @SamiKuhmonen The difference is in what the variable m_a contains at different places in the execution of the code, as can be seen in the print outs to the screen. – Mats Jun 11 '20 at 13:03
  • @SamVarshavchik I agree with what you say, but why does the variable m_a behave different when the thread is launched like in version 3? – Mats Jun 11 '20 at 13:04
  • Undefined behavior can manifest itself differently across different executions. And that's if the compiler even chose to emit that code in the first place. The fact that 2 different executions may see the same behavior is completely arbitrary – Human-Compiler Jun 11 '20 at 13:09
  • ~`start_thread_4` shares this problem as well. You may return the `ThreadTest`, but the pointer you pass to `std::async` will refer to the local `ThreadTest` that is destroyed at the end of the scope.~ `start_thread_2` just creates a `shared_ptr` that points to null, since you don't actually `make_shared` an object. Is that intentional? – Human-Compiler Jun 11 '20 at 13:13
  • @Human-Compiler OK, makes sense. But, is it then OK to do like I do in variant 3? I want a thread to be running, doing its stuff, until I signal with g_stop to it that it should stop. Am I in 3 passing the object into the thread and thus avoid it being destroyed, or how should I design this part? – Mats Jun 11 '20 at 13:14
  • @Human-Compiler Not using make_shared is a mistake. – Mats Jun 11 '20 at 13:15
  • Whoops, I didn't actually realize `start_thread_3` and `start_thread_4` are passing by value. My earlier point about `start_thread_4` sharing this problem is incorrect. Variant 3 and 4 should be fine because any `args...` passed into `std::async` are decayed first which causes it to construct a local copy of the arguments, similar to what happens with `std::thread`'s constructor – Human-Compiler Jun 11 '20 at 13:20

0 Answers0