Is It Safe to Read an Integer Variable That's Being Concurrently Modified Without Locking

Is it safe to read an integer variable that's being concurrently modified without locking?

atomic read
As said before, it's platform dependent. On x86, the value must be aligned on a 4 byte boundary. Generally for most platforms, the read must execute in a single CPU instruction.

optimizer caching
The optimizer doesn't know you are reading a value modified by a different thread. declaring the value volatile helps with that: the optimizer will issue a memory read / write for every access, instead of trying to keep the value cached in a register.

CPU cache
Still, you might read a stale value, since on modern architectures you have multiple cores with individual cache that is not kept in sync automatically. You need a read memory barrier, usually a platform-specific instruction.

On Wintel, thread synchronization functions will automatically add a full memory barrier, or you can use the InterlockedXxxx functions.

MSDN: Memory and Synchronization issues, MemoryBarrier Macro

[edit] please also see drhirsch's comments.

Can one thread safely read from a variable written on by another thread without guards?

Can one thread “safely” read from a variable written on by another thread without guards?

Yes, with the pre-condition that both operations are ordered in relation to one another. Generally, a requirement for that condition to be satisfied is for both operations to be atomic, and that is satisfied for atomic types. Read and write operations on non-atomic types are unordered in relation to operations in other threads unless that ordering is established through mutual exclusion (see std::mutex). Unordered operations across threads where at least one is write result in undefined behaviour.

int that you used in the example is not guaranteed to be an atomic type in C++. Instances of the std::atomic template are guaranteed to be atomic - well, the standard ones at least; if you define custom specialisations, then make sure to use mutual exclusion to keep them atomic to avoid confusion.

Would a single integer be safe to read while another thread might be writing in it?

In short: no, this is UB by C++ standard and you should abide by this rule.

Longer explanation: in x86, read and writes to properly aligned memory locations no larger than word size are atomic. However, without synchronisation, compiler can optimise code in non-obvious way: caching values, reordering reads, etc. In these case you might find that changes from one thread are not visible in another at all. Other funny things might happen if you expect some thing to happen in particular order.

Use atomic<T> type. It is geared towards perfomance, and might be even lock-free on platform which supports it.

Do I need to use locking with integers in c++ threads

You are never locking a value - you are locking an operation ON a value.

C & C++ do not explicitly mention threads or atomic operations - so operations that look like they could or should be atomic - are not guaranteed by the language specification to be atomic.

It would admittedly be a pretty deviant compiler that managed a non atomic read on an int: If you have an operation that reads a value - theres probably no need to guard it. However- it might be non atomic if it spans a machine word boundary.

Operations as simple as m_counter++ involves a fetch, increment, and store operation - a race condition: another thread can change the value after the fetch but before the store - and hence needs to be protected by a mutex - OR find your compilers support for interlocked operations. MSVC has functions like _InterlockedIncrement() that will safely increment a memory location as long as all other writes are similarly using interlocked apis to update the memory location - which is orders of magnitude more lightweight than invoking a even a critical section.

GCC has intrinsic functions like __sync_add_and_fetch which also can be used to perform interlocked operations on machine word values.

Is it dangerous to read global variables from separate threads at potentially the same time?

§1.10 [intro.multithread] (quoting N4140):

6 Two expression evaluations conflict if one of them modifies a
memory location (1.7) and the other one accesses or modifies the same
memory location.

23 Two actions are potentially concurrent if

  • they are performed by different threads, or
  • they are unsequenced, and at least one is performed by a signal handler.

The execution of a program contains a data race if it contains two
potentially concurrent conflicting actions, at least one of which is
not atomic, and neither happens before the other, except for the
special case for signal handlers described below. Any such data race
results in undefined behavior.

Purely concurrent reads do not conflict, and so is safe.

If at least one of the threads write to a memory location, and another reads from that location, then they conflict and are potentially concurrent. The result is a data race, and hence undefined behavior, unless appropriate synchronization is used, either by using atomic operations for all reads and writes, or by using synchronization primitives to establish a happens before relationship between the read and the write.

Is it safe to read global data from multiple threads?

Typically yes, but it's not a hard guarantee.

The core problem is that you need a memory barrier to make sure that the first thread writes back all data from registers to memory. However, it is very likely that this will happen when you create the second thread.

C - safety when accessing an integer variable: 1 writer, N readers

Usually, the atomicity of reading and writing a storage location is the same.

That is to say, if a location cannot be written atomically, it also cannot be read atomically and vice versa.

If a special atomic write is required, it is meaningless to use it unless the reads are also atomic.

For instance, imagine that the 64 bit location is being read using an ordinary read which requires, say, two 32 bit accesses. Suppose the write comes between those two accesses. The read will fetch the second 32 bits from the new value and combine it with the outdated first 32 bits. The only way the write cannot come between the two halves of the read is if the read is atomic. The atomic read knows how to properly interact with atomic write to prevent this situation.

You might be confused by "exceptions" to this rule. In some systems you might see an atomic update such as an increment, mixed with ordinary reads. This is predicated on the assumption that reads and writes are in fact atomic; the special atomic increment is only used to make read/modify/write cycle appear indivisible from the perspective of concurrent writers: if N writers perform this increment at around the same time, it ensures that the location is incremented by N.

Sometimes you might also see correct optimizations whereby an ordinary read is used, even though the underlying data type isn't atomically accessed. In such a situation, the algorithm doesn't care that it reads a "half baked" value.

For instance, in order to simply monitor a memory location to detect a change, you do not require an atomic read. Detecting a change doesn't require retrieval of the correct value. For instance of 0x00000000 is updated to 0x00010001, but the non-atomic read observes the intermediate value 0x00010000, that is still good enough for detecting that the location has changed.

If you must ensure that readers never see a half-baked value, then use atomic reads and writes.

There are other issues, like ordering. Suppose that a writer updates two locations, A and B. In some computing systems, it is possible for a reader to observe the update of B before A. Special "barrier" or "fence" instructions have to be used in addition to any atomic update instructions.

In a higher-level language, the API for these barriers may be built into the semantics of some atomic operations, so you may end up using the atomic instructions just for the sake of these barriers, even if the datum is otherwise atomic.



Related Topics



Leave a reply



Submit