0

I am updating an atomic variable size_t using from one thread and reading it from another. Following is the code:

Code:

// MyClass.hpp
#pragma once
#include <atomic>

class MyClass {
public:
    size_t GetVal() {
        return m_atomic_val;
    }

    void SetVal(const std::size_t val) {
        m_atomic_val = val;
    }

private:
    std::atomic<size_t> m_atomic_val{0};
};


// main.cpp
#include "MyClass.hpp"

#include <iostream>
#include <thread>

int main() {
    MyClass obj;
    obj.SetVal(4);

    std::thread my_thread = std::thread([&obj]{
       std::cout << "Thread started" << std::endl;
       std::this_thread::sleep_for(std::chrono::milliseconds(30));
        obj.SetVal(8);
    });

   std::this_thread::sleep_for(std::chrono::seconds(2));
   auto val = obj.GetVal();
   std::cout << "The value is: " << val << std::endl;
   my_thread.join();
}

Question:

  • But as you can see, I am updating m_atomic_val which is a std::atomic<size_t> with a size_t which is non atomic. Will this have bad repercussions? Is this illegal?

  • The return type of GetVal is size_t but its returning a std::atomic<size_t>. Is this wrong?

  • So, my primary quesion is that is mixing of atomic and non atomic variables allowed the way I am doing in the sample code?

  • This is only a simple example to demonstrate the question I have. What if there was one writer thread and one reader thread which are running concurrently and race conditions are very likely? Should I switch to using mutexes instead of atomics?

Environment:
My code runs on iOS, Android and macOS.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
AdeleGoldberg
  • 1,289
  • 3
  • 12
  • 28
  • For the first part, you have the statement `m_atomic_val = val;`. Would you still wonder about this if you instead had `m_atomic_val = 8;`? – Some programmer dude Feb 05 '20 at 08:34
  • If it was `m_atomic_val = 8`, then I wouldn't really worry about it. I get your point. So, `std::atomic = size_t` doesn't really matter. Correct? – AdeleGoldberg Feb 05 '20 at 08:40

1 Answers1

2

Assignment to an atomic object will accept any value of the correct type.

Since you have an std::atomic<size_t> then any value or variable that is (or can be implicitly converted to) a size_t can be used on the right-hand side of the assignment.

As for the GetVal issue, std::atomic have a conversion operator which will do the right thing, atomically fetch the value and give it to you for the return.

Another note regarding the GetVal function: It will only be the fetching of the value that will be atomic, the return of it will not be. So between the fetch and the actual return the value of m_atomic_val can change, but the old value will be returned.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • 2
    TL:DR: `std::atomic` overloads `operator=` as a seq_cst `.store(val)`, and conversion to the underlying type as a seq_cst `.load()`. (as you showed with links). The text of your description in terms of magic that happens on assignment or read also covers C11 `_Atomic size_t` which works the same way, with the magic happening inside the compiler, not a library template class. – Peter Cordes Feb 05 '20 at 08:47