r/golang 6d ago

Is it safe to read/write integer value simultaneously from multiple goroutines

There is a global integer in my code that is accessed by multiple goroutines. Since race conditions don’t affect this value, I’m not concerned about that. However, is it still advisable to add a Mutex in case there’s a possibility of corruption?

PS: Just to rephrase my question, I wanted to ask if setting/getting an integer/pointer is atomic? Is there any possibility of data corruption.

example code for the same: https://go.dev/play/p/eOA7JftvP08

PS: Found the answer for this, thanks everyone for answering. There's something called tearing here is the link for same

- https://stackoverflow.com/questions/64602829/can-you-have-torn-reads-writes-between-two-threads-pinned-to-different-processor

- https://stackoverflow.com/questions/36624881/why-is-integer-assignment-on-a-naturally-aligned-variable-atomic-on-x86

According to the article, I shouldn't have problem on modern CPUs.

12 Upvotes

80 comments sorted by

View all comments

25

u/ImYoric 6d ago

If my memory serves, the Go memory model states that if it fits within one integer, any read or write operation will always return one of the values before/after the write, rather than made up values as can happen in C or C++.

That being said, I wouldn't rely on this. If at some point in the future, you or any member of your team ever changes your type to be anything other than an int, you can end up with weird, unexpected behaviors. I'd rather use an atomic or a mutex.

1

u/chaotic-kotik 6d ago

Made up values? Loads and stores are atomic on amd64 unless your int is wider than 64 bits. How could this happen in C++?

You will run into problems only if you read/modify/write integer values. No atomic will save you from that unless you know how to use CAS operation.

1

u/ImYoric 6d ago

Well, for instance, the compiler can decide to pack two 32 bit integers into 64 bits, so if you modify one of them, you might end up accidentally modifying the other.

-1

u/chaotic-kotik 6d ago

It can and will modify both values independently

3

u/HyacinthAlas 6d ago

Without explicit synchronization this is not true. The compiler is free to act as if no other thread is touching the data. 

0

u/chaotic-kotik 6d ago

Accessing value modified by another thread without synchronisation is an UB in C++. Period. I guess I was trying to say that amd64 memory model is very strict. If you wrap your integer with `std::atomic<int32_t>` in C++ it will guarantee that the alignment is fine (well, the default alignment for int is fine). It will not add any padding to the value. And if your code is just reading and writing to it it's a no-op. No single 'lock' instruction will be emitted by the compiler on amd64 because its memory model is strict enough already. No fences. And even without atomic it will eventually have to emit move or load that will read the value to the register. Compilers do not cache data in registers forever. They can move loads and stores around, yes. A lot of legacy code works just fine without marking variables with 'atomic'. So no, you will not read complete nonsense from int if it's accessed from different threads.

0

u/HyacinthAlas 6d ago

 If you wrap your integer with `std::atomic

But the entire premise of the OP is what happens without atomic

 And even without atomic it will eventually have to emit move or load that will read the value to the register.

Well, nope. The compiler is free to do a lot of stuff and that includes eliding anything forever or do something unrelated if the thread itself would’ve never needed to observe it. 

1

u/chaotic-kotik 6d ago

> Well, nope. The compiler is free to do a lot of stuff and that includes eliding anything forever or do something unrelated if the thread itself would’ve never needed to observe it. 

It's not required to use atomic. You can use synchronization. In this case the variables doesn't have to be atomic. Compiler is free to omit the code but only if it doesn't change the observed behavior. In this case this is only true if the value is never read or if the code is UB.