Search code examples
c++windowsmultithreadingatomic

C++ Can an atomic variable be declared inside a structure to protect those members?


Have a structure declared and defined in shared file.

Both threads create by the Windows API CreateThread() have visibility to the instance of it:

struct info
{
    std::atomic<bool> inUse; 
    string name;

};
info userStruct; //this guy shared between two threads

Thread 1 continually locking/unlocking to write to member in structure (same value for test):

    while (1)
    {
        userStruct.inUse = true;
        userStruct.name= "TEST";
        userStruct.inUse = false;
    }   

Thread 2 just reading and printing, only if it happens to catch it unlocked

    while (1)
    {
        while (! userStruct.inUse.load() )
        {
            printf("** %d, %s\n\n", userStruct.inUse.load(), userStruct.name.c_str());
            Sleep(500); //slower reading
        }

        printf("In Use!\n");
    }

Expect to see a lot of:

"In Use!"

and every once if it gets in, when unlocked:

"0, TEST"

..and it does.

But also seeing:

"1, TEST"

If the atomic bool is 1 I do NOT expect to ever see that.

What am I doing wrong?


Solution

  • Your code is not thread safe. The atomic is atomic. But the if statement isn't !

    What happens:

    Thread 1                                Thread 2               Comment 
    
    while (! userStruct.inUse.load() )                             ---> InUse is false 
    ==> continues loop 
                                            inUse = true           
    ==> continues loop already started
    printf(...) 
    

    In the worst case you could have UB due to a data race (one thread 2 modifies the string, and thread 1 reads the string during the modification).

    Solution:

    Since you intend to use your atomic as a lock, just use a real lock designed for this kind of synchronisation, using a std::mutex with a std::lock_guard.

    For example:

    struct info
    {
        std::mutex access; 
        string name;
    }; 
    

    The first thread would then be:

    while (1)
    {
        std::lock_guard<std::mutex> lock(userStruct.access); // protected until next iteration
        userStruct.name= "TEST";
    }   
    

    The second thread could then attempt to gain access to mutex in a non-blocking fashion:

    while (1)
    {
        {  //  trying to lock the mutex
            std::unique_lock<std::mutex> lock(userStruct.access, std::try_to_lock);
            if(!lock.owns_lock()){   // if not successful do something else
                std::cout << "No lock" <<std::endl; 
            }
            else                     // if lock was successfull
            {
                std::cout << "Got access:" << userStruct.name <<std::endl;
            }
        } // at this stage, the lock is released.
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }
    

    Online demo