Hacker News new | ask | show | jobs
by wfox 1543 days ago
>... or only lock on writes but not reads...

Are you saying this is always wrong? I thought that depended on the use case at hand.

3 comments

If you only lock on writes rather than reads, it prevents two writers from colliding, but readers can see torn data (a mix of old and new states). Technically speaking, if either the write or read is non-atomic, this is outright undefined behavior, and it's legal for the compiler to generate (eg. reading) code which assumes concurrent writes never happen and misbehaves if they do (for example, the reader thread reading the value twice, expecting the value to be the same, and misbehaving if it's not).

To prevent tearing, you can use a regular mutex (only one reader at a time), or https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock, which allows multiple concurrent readers, but has more overhead than a regular mutex, and when contention occurs can prioritize either readers or writers. If you don't want readers or writers to block, you can use (for <=64-bit values) atomic reads and writes, (for single writers and readers) triple buffers, or (in general) RCU or hazard pointers.

If the value is small enough, like 1 byte, and you know for a fact that your CPU + RAM will always be able to update it atomically, then yes technically you could get away with not locking on reads.

BUT, for large values (structs) you will end up getting ragged reads if another thread is writing at the same time. And if another thread is not writing at the same time, what's the harm in locking for reads? Modern mutexes lock fast if there's no contention.

BUT ALSO, many operations are actually a read, followed by a related write. If you don't lock on reads, it's easy to accidentally compose multiple small, correct functions, into a large, incorrect function.

Your comment reinforces nyanpasu64 point that the average C++ developer doesn't understand multithreading. Your comment is wrong in that it is never correct in a standard conforming C++ program to read and write to the same object from different threads without the use of a memory barrier, even if the CPU + RAM supports it. The C++ compiler itself will not support such an access pattern and the compiler may reorder instructions that are not protected by a barrier in such a way that results in undefined memory access.

https://en.cppreference.com/w/cpp/language/memory_model

It is always wrong. Compiler optimization and CPU dark magic like instruction reorder will wreak havoc on your unprotected reads. That is what undefined behavior really means, not “it’s actually defined but we don’t want to tell you”.