Reputation: 415
class Foo {
public:
void set(uint64_t new_var) { var_ = new_var; }
uint64_t get() { return var_; }
private:
uint64_t var_;
};
Is there necessary need a lock in set
and get
if one writer multi-reader?
Upvotes: 1
Views: 152
Reputation: 85452
Is there necessary need a lock in
set
andget
if one writer multi-reader?
You don't necessarily need a lock, but you need synchronization, which can be achieved either by locking a mutex or simply declaring the variable atomic.
In C++: std::atomic<uint64_t> var_;
In C: _Atomic uint64_t var_;
Regardless of the data type, in both C and C++ languages every variable accessed concurrently when there is at least one writer involved, requires synchronization.
This has to do with the way the compiler orders instructions when generating code; in the absence of synchronization, as per the as-if rule, the compiler is allowed to assume each thread is executed in isolation, and reorder variable accesses any way it sees fit. Unsynchronized access immediately lands the program in the realm of undefined behavior as it becomes impossible to predict the outcome. For example, the variable can be optimized into a CPU register, and two threads will never notice it changing at all, or it may be written to memory at a different time in the program.
In addition, on many CPU architectures (not counting x86) 64-bit reads/writes are actually not atomic. So even if the code happens to be laid out favorably, it may still not work correctly.
Upvotes: 3
Reputation: 188
Yes, consider this example:
#include <iostream>
#include <future>
#include <vector>
class Foo
{
public:
void set(uint64_t new_var) { var_ = new_var; }
uint64_t get() { return var_; }
private:
uint64_t var_;
};
int main()
{
Foo v1; v1.set(0);
uint64_t v2 = 0;
std::vector<std::future<void>> ret_1;
ret_1.reserve(10);
std::vector<std::future<void>> ret_2;
ret_2.reserve(10);
for (int i = 0; i < 10; i++)
{
ret_1.push_back(std::async(std::launch::async, [&]() {
for (int j = 0; j < 10'000; j++)
v1.set(v1.get() + 1ul);
}));
ret_2.push_back(std::async(std::launch::async, [&]() {
for (int j = 0; j < 10'000; j++)
v2+=1ul;
}));
}
for(auto& f : ret_1)
f.wait();
for(auto& f : ret_2)
f.wait();
// Expected in both examples: 10'000 * 10 = 100'000
std::cout << "Foo: " << v1.get() << std::endl;
std::cout << "uint64_t: " << v2 << std::endl;
}
Expectred results is 100'000 but this is output:
raidg@Papuga:~$ c++ -o main main.cpp -lpthread
raidg@Papuga:~$ ./main
Foo: 26257
uint64_t: 32825
Because there is no sync between them
Upvotes: 0
Reputation: 1072
Yes, it is necessary. Besides of platform restrictions (see comment), your uint64_t can be unaligned: the reading can request 2 operations.
Upvotes: 0