I came across supposedly thread-safe code that both I and gcc thread sanitizer don't consider really thread-safe.
The code is something along these lines:
class thread_safe
{
public:
thread_safe(uint64_t size=0)
: size_{size}
{}
uint64_t get_size()
{
if(size_ == 0)
{
size_ = query_size();
if(size_ == 0)
throw std::runtime_error("couldn't get size");
}
return size_;
}
private:
// not certain this even matters but maybe vtable could make it worse so placing it here
virtual uint64_t query_size() = 0;
uint64_t size_;
};
My guess is that the author didn't want to pay for std::atomic
access since query_size() is guaranteed to always return the same value.
So if I consider the "integer assignment is an atomic operation" (not certain whether this is really true or if uint64_t still falls under any such reasoning since it's 64 and not 32 bit but ok...) and that query_size() always returns the same value so per-core-cache should not be an issue... then I can squint my eyes and try to convince myself that this really is thread-safe.
So could this be considered thread safe under C++ in general?
(or at least under some circumstances) (maybe only under C? but I doubt that there is a difference in the standard)
Is there a solution to make assignment inside get_size() function thread safe without changing size_ type or initialization location?
EDIT:
To be clear, I'm not saying that this code is well written, just want to understand if I can present a more solid case against it.
And regarding data race the code has two states:
if (in_current_thread_I_see_size_not_equal_0)
return size_
(size_ was set in some other thread and eventually came to this core)
and
if (in_current_thread_I_see_size_equal_to_0)
size_ = set_size_in_this_thread_to_same_value_that_will_be_written_in_other_threads
return size_
(size was not set in any other threads or the value from those writes hasn't reached this threads memory/register)
So in the end for each thread the "size_ already set to != 0" is just optimization (no need to read size from somewhere that could be expensive) and even though it's a race it's not necessarily an important one.
I'm interested into finding out if either standard or at least hardware supports the reasoning of the initial author of the code.