Archived

This topic is now archived and is closed to further replies.

CWizard

Quickie: Is iCounter += iSomeVal; thread-safe?

Recommended Posts

It's all in the subject. Will a incremental C statement be thread-safe, when the variable incremented is shared among threads? To narrow the question, leave out multi-processor systems. If I have understood it right, the (stupid, ed. note ) intel processor cannot do memory-2-memory operations in a single instruction. Is it so, or is the compiler likely to not use such instruction? There are a few (main) methods I can think of that the compiler could implement the following C-code, and give some sort of pseudo-assembly code for it (am no good at intel's syntax).
// global
int     iCounter = 0;

...
    // A thread executes this
    iCounter += iSomeVal;
  1. add <iCounter>, <iSomeVal>
  2. move eax, <iSomeVal> add <iCounter>, eax
  3. move eax, <iCounter> add eax, <iSomeVal> move <iCounter>, eax
The first and second method will be thread safe (in single-processor environment), but the last one won't. I know I could check the disassembly, but my experience tell me that you cannot be sure that it always do it that way. I thought this would be a short question, but it got a bit longer. Thanks for reading, and more so if you provide some aspect of this. EDIT: some formatting probs. [edited by - CWizard on July 28, 2002 3:26:54 PM]

Share this post


Link to post
Share on other sites
No one answers my post

Okay, I did the disassembly and got this:

28: int iCounter = 12;
0040DBE2 mov dword ptr [ebp-4],0Ch
29: int iSomeVal = 10;
0040DBE9 mov dword ptr [ebp-8],0Ah
30:
31: iCounter += iSomeVal;
0040DBF0 mov eax,dword ptr [ebp-4]
0040DBF3 add eax,dword ptr [ebp-8]
0040DBF6 mov dword ptr [ebp-4],eax

As we can see from this, the idiotic compiler moves the value of iCounter into eax, then add the value of iSomeVal from memory to eax, and then put eax back into iCounter. That is, the last method example I provided in the original post, and IS NOT THREAD-SAFE. Why does it do this? Can''t it even add from register onto a memory location?

Couldn''t it do something like this:

mov eax,dword ptr [ebp-8] ; move from iSomeVal
add dword ptr [ebp-4],eax ; add to iCounter

Share this post


Link to post
Share on other sites
quote:
Original post by CWizard
Can''t it even add from register onto a memory location?



Nope.
The x86 is very limited in odd ways, like not being able to move a value into a segment register (Ok, that''s 16 bit, but it''s just an example).

It''s just not wired to move from a register into memory, AND add in the same instruction.

Share this post


Link to post
Share on other sites
So, then it isn''t possible to do this thread-safe, unless you use some sort of blocking technique?
quote:
It''s just not wired to move from a register into memory, AND add in the same instruction.
Although, it does the other way around; move from memory to register AND add. Well... I''ll continue to be disappointed with Intel, and stay worshipping motorola

Share this post


Link to post
Share on other sites
As you found out, it''s not thread-safe. However, there are Win32 calls available for this situation:

InterlockedIncrement
InterlockedDecrement
InterlockedExchange
InterlockedExchangeAdd

The last one is what you''re looking for.

-- Voivod

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Just use a semaphore. It''s not like it''s really a blocking busy wait... your processor will move to another thread until the semaphore is released.

Share this post


Link to post
Share on other sites
quote:
Original post by Anonymous Poster
Just use a semaphore. It''s not like it''s really a blocking busy wait... your processor will move to another thread until the semaphore is released.


That''s a bit heavyweight. The best thing is InterlockedExchangeAdd which is far more lightweight, even if you only take into account the fact that it results in 1 system call rather than 2 for a semaphore. (Though it''s faster for other reasons as well)

If I had my way, I''d have all of you shot!


codeka.com - Just click it.

Share this post


Link to post
Share on other sites
Sorry for perhaps being a bit bitchy here, but it's not directed towards you guys; I'm thankful for you providing the info.

Voivod: That function performs what I need. But, isn't that just a bit bloated for such an easy task? To do this on my Amiga (as an all-memory operation) I would simply type this:
add.l iSomeVal(a5), iCounter(a5)

(where a5 is a register pointing to some structure which iSomeVal and iCounter are offsets into)
That would be a single instruction of, i believe, 6 bytes, taking max 6 cycles (3 if best case). Oh man, no wonder my 30 MHz Amiga 1200 seem to run faster than this PIII piece of crap...

EDIT:
quote:
If I had my way, I'd have all of you shot!
I like this one !

[edited by - CWizard on July 28, 2002 6:21:51 PM]

Share this post


Link to post
Share on other sites
Well, sure, it would be nice if you didn''t need system call to synchronize an increment or add operation. Unfortunately, that''s how it is...

I just wanted to point out that there are pre-built functions for this purpose, in case you weren''t aware of them, since it''s cleaner to use those functions than to create critical sections all over the place.

Share this post


Link to post
Share on other sites
This thread should probably just die out, but just one more question here. I checked the disassembly of the InterlockedExchangeAdd(), which was:
77E76B5C   mov         ecx,dword ptr [esp+4]
77E76B60 mov eax,dword ptr [esp+8]
77E76B64 lock xadd dword ptr [ecx],eax
77E76B68 ret 8
. But, before that, a lot of overhead. Could one instead make this a inline unsing a macro? What instruction is that "lock xadd", and which processors support it?

Share this post


Link to post
Share on other sites