Vegar Westerlund
Vegar Westerlund

Reputation: 1624

Hazards of not protection shared variables in a threaded environment

I'm trying to understand the hazards of not locking shared variables in a threaded (or shared memory) environment. It is easy to argue that if you are doing two or more dependent operations on a variable it is important to hold some lock first. The typical example is the increment operation, which first reads the current value before adding one and writing back.

But what if you only have one writer (and lots of readers) and the write is not dependent on the previous value. So I have one thread storing a timestamp offset once every second. The offset holds the difference between local time and some other time base. A lot of readers use this offset to timestamp events and getting a read lock for each time is a little expensive. In this situation I don't care if the reader gets the value just before the write or just after, as long as the reader don't get garbage (that is an offset that was never set).

Say that the variable is a 32 bit integer. Is it possible to get a garbage read of the variable in the middle of a write? Or are writing a 32 bit integer an atomic operation? Will it depend on the Os or hardware? What a about a 64 bit integer on a 32 bit system?

What about shared memory instead of threading?

Upvotes: 1

Views: 386

Answers (6)

Steve Townsend
Steve Townsend

Reputation: 54138

Platforms often provide atomic read/write access (enforced at the hardware level) to primitive values (32-bit or 64-bit,as in your example) - see the Interlocked* APIs on Windows.

This can avoid the use of a heavier weight lock for threadsafe variable or member access, but should not be mixed up with other types of lock on the same instance or member. In other words, don't use a Mutex to mediate access in one place and use Interlocked* to modify or read it in another.

Upvotes: 0

MSN
MSN

Reputation: 54554

The platform you run on determines the size of atomic reads/writes. Generally, a 32-bit (register) platform only supports 32-bit atomic operations. So, if you are writing more than 32-bits, you will probably have to use some other mechanism to coordinate access to that shared data.

One mechanism is to double or triple buffer the actual data and use a shared index to determine the "latest" version:

write(blah)
{
    new_index= ...; // find a free entry in the global_data array.
    global_data[new_index]= blah;
    WriteBarrier(); // write-release
    global_index= new_index;
}

read()
{
    read_index= global_index;
    ReadBarrier(); // read-acquire
    return global_data[read_index];
}

You need the memory barriers to ensure that you don't read from global_data[...] until after you read global_index and you don't write to global_index until after you write to global_data[...].

This is a little awful since you can also run into the ABA issue with preemption, so don't use this directly.

Upvotes: 0

I GIVE CRAP ANSWERS
I GIVE CRAP ANSWERS

Reputation: 18859

In additions to the above comments, beware the register bank in a slightly more general setting. You may end up updating only the cpu register and not really write it back to main memory right away. Or the other way around where you use a cached register copy while the original value in memory has been updated. Some languages have a volatile keyword to mark a variable as "read-always-and-never-locally-register-cache".

The memory model of your language is important. It describes exactly under what conditions a given value is shared among several threads. Either this is the rules of the CPU architecture you are executing on, or it is determined by a virtual machine in which the language is running. Java for instance has a separate memory model you can look at to figure out what exactly to expect.

Upvotes: 2

Adam Miezianko
Adam Miezianko

Reputation: 148

It very much depends on hardware and how you are talking to it. If you are writing assembler, you will know exactly what you get as processor manuals will tell you which operations are atomic and under what conditions. For example, in the Intel Pentium, 32-bit reads are atomic if the address is aligned, but not otherwise.

If you are working on any level above that, it will depend on how that ultimately gets translated into machine code. Be that a compiler, interpreter, or virtual machine.

Upvotes: 0

Zooba
Zooba

Reputation: 11438

An 8-bit, 16-bit or 32-bit read/write is guaranteed to be atomic if it is aligned to it's size (on 486 and later) and unaligned but within a cache line (on P6 and later). Most compilers will guarantee stack (local, assuming C/C++) variables are aligned.

A 64-bit read/write is guaranteed to be atomic if it is aligned (on Pentium and later), however, this relies on the compiler generating a single instruction (for example, popping a 64-bit float from the FPU or using MMX). I expect most compilers will use two 32-bit accesses for compatibility, though it is certainly possible to check (the disassembly) and it may be possible to coerce different handling.

The next issue is caching and memory fencing. However, the effect of ignoring these is that some threads may see the old value even though it has been updated. The value won't be invalid, simply out of date (by microseconds, probably). If this is critical to your application, you will have to dig deeper, but I doubt it is.

(Source: Intel Software Developer Manual Volume 3A)

Upvotes: 1

Ned Batchelder
Ned Batchelder

Reputation: 375484

Writing a 64-bit integer on a 32-bit system is not atomic, and you could have incorrect data if you don't take a lock.

As an example, if your integer is

0x00000000 0xFFFFFFFF

and you are going to write the next int in sequence, you want to write:

0x00000001 0x00000000

But if you read the value after one of the ints is written and before the other is, then you could read

0x00000000 0x00000000

or

0x00000001 0xFFFFFFFF

which are wildly different than the correct value.

If you want to work without locks, you have to be very certain what constitutes an atomic operation on your OS/CPU/compiler combination.

Upvotes: 3

Related Questions