views:

308

answers:

7

This is a simplified version of some code I'm currently maintaining:

int SomeFunc() 
{
  const long lIndex = m_lCurrentIndex;
  int nSum = 0;
  nSum += m_someArray[lIndex];
  nSum += m_someArray[lIndex];
  return nSum;
}

lCurrentIndex is updated periodically by another thread. The question is; will making a local copy of m_CurrentIndex make sure both accesses to m_someArray uses the same index?

Please note that this is a simplified example; I'm thinking about the concept of making a local copy, not the exact piece of code shown here. I know the compiler will put the value in a register, but that is still a local copy as opposed to reading from lCurrentIndex twice.

Thanks!

Edit: The initial assignment is safe, both are guaranteed to be 32 bit in our setup. Edit2: And they are correctly aligned on a 32bit boundary (forgot about that one)

+6  A: 

will making a local copy of m_CurrentIndex make sure both accesses to m_someArray uses the same index?

In the same execution of SomeFunc, yes, of course. A local integer variable (lIndex) will not magically change its value in the middle of the function.

Of course, the following are also true: the actual value of m_someArray[lIndex] (as opposed to that of lIndex) might change; m_someArray in itself might change; and what Neil said about the validity of lIndex's initial value.

Daniel Daranas
Wrong, because `m_CurrentIndex` is `long`, and there are platforms on which copying that isn't atomic.
Pavel Minaev
@Pavel, again, I'm asking: are you saying that the further appearances of lIndex in lines 3 and 4 of the function might result in using a different value?
Daniel Daranas
I agree with Daniel. The question is whether lIndex remains same or not to which the answer is obvious yes. Whether that lIndex is valid or not is a totally different matter.
Naveen
+3  A: 

Yes the copy of the current index will make sure both array accesses use the same index. That is not really what I would have thought of as "thread safe" though. You need to concern yourself with concurrent access to shared variables. To me that looks like the access to the array could be an area of potential concern as well.

1800 INFORMATION
+15  A: 

No, the initialisation of the local, which reads a shared variable, is not necessarily atomic. (consider what code would be needed on an 8-bit platform, for example) In general, the only way to write thread safe code is to use atomic operations specified by your compiler and/or OS, or to use OS locking features.

anon
+1. Without analyzing emitted code you can't even be sure that the read will be done in a single instruction.
sharptooth
Are you saying that once const long lIndex is initialized, and while in the same execution path from that point to the end of the function, its value may change?
Daniel Daranas
No, I'm saying it may have an indeterminate value (because of threading issues) immediately following initialisation.
anon
I take it that you were answering to the general "Is this code thread-safe?" question and not to the second, more specific and rather different, "will making a local copy of m_CurrentIndex make sure both accesses to m_someArray uses the same index?".
Daniel Daranas
If the index is indeterminate, the code isn't guaranteed to access ANY array element, much less the same one.
anon
On a 32-bit platform, a `long` _may_ be 64-bit, and in particular _need not_ be atomic. The latter means that assigning a `long` value may be done in several steps, and another thread might change the source value _in between_ those steps - so you end up with half the bits from the old value, and half the bits from the new one. That said, on x86, `long` is traditionally 32-bit and atomic.
Pavel Minaev
So you say that { const long lIndex = m_lCurrentIndex; cout << lIndex; cout << lIndex; } might result in an output such as (ignoring formatting) "14 22"?
Daniel Daranas
@Pavel "so you end up with half the bits from the old value, and half the bits from the new one". But you still end up with *a value*, one which will be the same at lines 3 and 4 where it is used. (Of course nothing can be assumed about the m_... array, that's another issue).
Daniel Daranas
@Daniel I am saying that assuming mICurrentIndex contains value X when the initialisation starts, there is no guarantee that lIndex contains X when it ends. There is also no guarantee that the value it it does contain is related to X in any obvious way, or that it is a valid array index for the array.
anon
@Neil I understand. I was focusing in that "Once initialized, lIndex remains unchanged throughout the function's execution path", and you were focusing on "lIndex's initial value may be indeterminate, hence it cannot be assumed that the operation someArray[lIndex] is meaningful or even legal".
Daniel Daranas
Even *if* you are on a 32 bit platform, and even *if* the long is 32 bit, if m_lCurrentIndex is misaligned, the read will still be non-atomic. Also, you might read a stale value.
peterchen
A: 

Yes, it will access the same element of the array. It is like you are taking a snapshot of the value of m_lCurrentIndex into the local variable. Since the local variable has its own memory whatever you do to m_lCurrentIndex will have no effect on the local variable. However, note that since the assignment operation is not guaranteed to be atomic you may very well end up with an invalid value in lIndex. This happens if you update m_lCurrentIndex from one thread and at the same time try the assignement into lIndex from the other thread.

Naveen
Thanks for comments, all of you (and sorry I made a duplicate dunno what happened).First of all; the initial assignment is safe. Both long and int are guaranteed to be 32bit in our setup.Qoute: [It is like you are taking a snapshot of the value of m_lCurrentIndex into the local variable. Since the local variable has its own memory whatever you do to m_lCurrentIndex will have no effect on the local variable]The local variable doesn't have it's own memory. I had a look at the asm and the compiler skips the temp variable and put the value directly into a register.
+1  A: 

Another question to ask here is: is the copy operation from m_lCurrentIndex to lIndex an atomic operation? If it isn’t you might end up using very weird values which will probably do nothing good. :)

Point is: when you are using multiple threads there won’t be a way around some kind of locking or synchronization.

Bombe
I agree with the first part of the answer, I disagree with the second. Quite often there is way how to program thread safe and still avoid locking or other synchronization primitives, which is the subject of lock-less programming.
Suma
+1  A: 

Concept of local copy

I'm thinking about the concept of making a local copy, not the exact piece of code shown here.

This question cannot be answered without knowing more details. It boils down into the questions if this "making a local copy" of m_lCurrentIndex into lIndex is atomic.

Assuming x86 and assuming m_lCurrentIndex is DWORD aligned and assuming long represents DWORD (which is true on most x86 compilers), then yes, this is atomic. Assuming x64 and assuming long represents DWORD and m_lCurrentIndex is DWORD aligned or long represents 64b word and m_lCurrentIndex is 64b aligned again yes, this is atomic. On other platforms or without the alignment guarantee two or more physical reads may be required for the copy.

Even without local copy being atomic you still may be able to make it lock-less and thread safe using CAS style loop (be optimistic and assume you can do without locking, after doing the operation check if everything went OK, if not, rollback and try again), but it may be a lot more work and the result will be lock-less, but not wait-less.

Memory barries

A word of caution: once you will move one step forward, you will most likely be handling multiple variables simultaneously, or handling shared variables which act as pointers or indices to access other shared variables. At that point things will start more and more complicated, as from that point you need to consider things like read / write reordering and memory barriers. See also How can I write a lock free structure

Suma
+3  A: 

This should be thread safe (at least it will on all the compilers/OSes I've worked with). However, to be extra doubly sure you could declare m_lCurrentIndex as volatile. Then the compiler will know that it might change at any time.

Greg Hewgill
See the thread this is a dupe of. and you are wrong about volatile.
anon
"extra doubly"? Can e.g. space-optimizations bypass the local variable? Otherwise I don't see how this can be unsafe.
xtofl
I don't know of a system that would bypass the local variable. Using `volatile` is very much a belt-and-braces solution.
Greg Hewgill
@Neil Butterworth: I didn't know about the dupe when I answered. However, I don't see discussion of "volatile" anywhere on the other page. Could you elaborate?
Greg Hewgill