How to properly assign a member value in a std::thread and read it from another?

Viewed 39

I have an issue with multithreading where I probably lack understanding. I want to assign a member variable of an object inside a thread and then print the variable from another thread (or main in this case). It seems like I am doing something wrong, like there are two objects.

Here is a quick example:

thread_test.hpp

#pragma once                           
                                       
#include <iostream>                    
#include <chrono>                      
#include <thread>                      
                                       
class thread_test                      
{                                      
    public:                            
        thread_test ():                
            test_variable{}            
        {}                             
                                       
        int get_test_variable() const; 
        void updater_method();         
    private:                           
        /* private data */             
                                       
        int test_variable;             
};                                     

thread_test.cpp

#include "thread_test.hpp"                                           
                                                                     
void thread_test::updater_method()                                   
{                                                                    
    int i{};                                                         
                                                                     
    while(true)                                                      
    {                                                                
        test_variable = ++i;                                         
        std::this_thread::sleep_for(std::chrono::microseconds{1800});
                                                                     
    }                                                                
}                                                                    
                                                                     
int thread_test::get_test_variable() const                           
{                                                                    
    return test_variable;                                            
}                                                                    

main.cpp

#include <iostream>
#include <thread>
#include <unistd.h>
                                                                    
#include "thread_test.hpp"
                                                                    
int main()
{
                                                                    
    thread_test obj{};
                                                                    
    std::thread updater_thread(&thread_test::updater_method, obj);
                                                                    
    while(true)
    {
        std::cout << obj.get_test_variable() << std::endl;
        sleep(1);
    }
                                                                    
    return 0;
}

Output:

❯ ./main
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0

I don't understand why the variable stays at the initialized value, because I am assigning it in the thread for the test object. Is there a better way to start the thread or should I start the thread from the class itself? What is the right way to do this?

1 Answers

std::thread stores the arguments to be passed to the thread execution function by value (actually the function as well, relevant, if it's actually a functor, which is supported as well) – i.e. it creates copies of, which applies, too, to your test object obj. So the result is that the main thread and the worker thread operate each on their own, distinct instance of your thread_test class and thus the main thread never receives updates.

To avoid, you can either use a std::reference_wrapper or a pointer:

std::thread updater_thread(&thread_test::updater_method, std::ref(obj));
// or alternatively:
std::thread updater_thread(&thread_test::updater_method, &obj);

This way you only operate on one single instance. Yet another alternative (thanks, @chi, for the hint) is using a lambda:

std::thread updater_thread([&obj]() { obj.updater_method() });
//                           ^^^ optional, alternatively just a default &

Note, too, that updating and reading variables in general is not atomic and you might get confronted with race conditions. You can solve the issue by either using std::atomic, wrapping entire objects into or single members thereof, whichever suits better for you, or you protect access to the variables via a std::mutex or possibly a std::shared_mutex, if you want to allow simultaneous reads and only have the writes with exclusive access.

Related