From Box's article, the first function is for single thread, the second is
for multithread.
STDMETHODIMP_(ULONG) CPager::AddRef() {
return ++m_dwRef;
}
STDMETHODIMP_(ULONG) CPager::AddRef() {
return InterlockedIncrement(&m_dwRef);
}
My problem with this is that I thought that incrementing and integer was an
atomic operation anyway. So where is the potential for corrupting this
variable?
The SDK doc says that InterlockedIncrement increments and checks the
variable. Checks against what? The return is just the incremented variable.
Any insight into this will be appreciated.
Joe
> My problem with this is that I thought that incrementing and integer was
an
> atomic operation anyway. So where is the potential for corrupting this
> variable?
>
Incrementing a 32 bit value is atomic (at least on 80386 and later, AFAIK),
but incrementing _and_ returning the value is not.
Matthias
STDMETHODIMP_(ULONG) CPager::AddRef() {
return ++m_dwRef;
}
STDMETHODIMP_(ULONG) CPager::AddRef() {
return InterlockedIncrement(&m_dwRef);
}
Thanks for your help Matthias.
Joe
"Matthias Meyer" <matthia...@softron.de> wrote in message
news:a55p07$4tn71$1...@ID-114817.news.dfncis.de...
Conceptually the code from your compiler may well be:
read half of i into temp.
-> Interrupt possible.
read second half i into temp.
-> Interrupt possible.
Increment temp.
-> Interrupt possible.
store half of temp to i
-> Interrupt possible
store second half of temp to i
-> Interrupt possible
return temp.
In many of the situations above, a context switch to another
thread accessing 'i' will cause bad things to happen...
Now, any good compiler will do better, but still the point is, for
multithreading code, you must use the OS-provided atomic
operations if you want a guarantee that your code will work.
The above is for a single-processor situation. In a SMP-
environement where the threads may execute on independent
processors it gets even worse. Not even an assembly level
'INC' is atomic then. I don't think any C-compiler will
generate the LOCK prefix (in an intel processor) for ++ operator
or any other code guaranteeing atomic operations in an
SMP situation.
So - You really need to use either assembler or the OS-provided
interaces to those facilities, notable InterlockedIncrement() et. al.
/Svante
Joe
"Svante" <spl...@nospam.com> wrote in message
news:%lud8.27558$l93.5...@newsb.telia.net...
If you look at your original example, the data that InterlockedIncrement
works on aren't on the stack (I assume m_dwRef is a member variable). The
difference is that InterlockedIncrement(&m_dwRef) is guaranteed (at the
assembly code level) to be atomic, whereas m_dwRef++ isn't.
--
Tim Robinson
http://www.themoebius.org.uk/
STDMETHODIMP_(ULONG) CPager::AddRef() {
return InterlockedIncrement(&m_dwRef);
}
STDMETHODIMP_(ULONG) CPager::AddRef() {
m_dwRef = InterlockedIncrement(&m_dwRef);
return m_dwRef;
}
"Tim Robinson" <timothy.rem...@ic.ac.uk> wrote in message
news:a55scc$4u4rh$1...@ID-103400.news.dfncis.de...
Ignore the return value from AddRef() (it's only ever intended to be a
debugging aid and not to be taken seriously anyway).
I'll paraphrase what Svante wrote. Imagine we have two threads (maybe on
different processors, maybe on the same one), both using the same COM
object, and each calls AddRef() almost simultaneously.
Say we use a naive m_dwRef++ approach, which might compile to:
_CPager_AddRef:
mov eax, [m_dwRef] ; load variable into register
inc eax ; increment register
mov [m_dwRef], eax ; store varaible into memory
ret
Run through the code in your head, for each thread. (Remember each thread
gets its own registers; if it helps, imagine that each thread is on a
different CPU.)
Thread 1: Load into register = 0
-- Context switch --
Thread 2: Load into register = 0
Thread 2: Increment = 1
-- Context switch --
Thread 1: Increment = 1
Thread 1: Store into memory = 1
-- Context switch --
Thread 2: Store into memory = 1
Thread 2: Return
-- Context switch
Thread 1: Return
So we can see that, instead of m_dwRef getting incremented twice (and
therefore getting the value 2), it ends up with 1 instead. If we use
InterlockedIncrement() instead, each thread is guaranteed not to get
interrupted before the increment operation has completed.
To sum up this thread:
- The need for InterlockedIncrement() has nothing to do
with the location of the object being incremented, i.e.
'auto' or 'static'.
- The need for InterlockedIncrement() remains even if
you only increment, and don't need the resulting/previous
value in an atomic operation.
- The need for InterlockedIncrement() remains even if
you think that "inc [ebx]" or some such instruction is
atomic on one CPU, as it is not on multi processor systems.
To really sum up: When you have multiple threads, either
you access objects with InterlockedXXX(), or you use
a CriticalSection or other thread synchronization primitives
to ensure that one, and only one, thread at a time access
that piece of data. Possibly, if you really know what you
are doing, you can write assembly code using the hardware
specific primitives to achieve atomic operations where
necessary.
/Svante
Joe
"Svante" <spl...@nospam.com> wrote in message
news:Luvd8.27586$l93.5...@newsb.telia.net...
[ ... ]
> My problem with this is that I thought that incrementing and integer was an
> atomic operation anyway. So where is the potential for corrupting this
> variable?
Incrementing an integer is _not_ necessarily atomic. A dword will
_normally_ be at least dword aligned, but that's not necessarily the
case. When/if it's not, even just _reading_ a dword isn't
necessarily atomic -- on a 386 or 486, if it starts at an odd
address, it'll be read as single byte, then two words, then the final
byte, as three separate bus transactions. If it starts at an address
that's even but not a multiple of four, it'll be read as two
successive words.
The Pentium has a substantially different external bus protocol, but
the long and short of it is that if the variable is at the wrong
address, reading it might still take place in a couple of separate
bus transactions, rather than being done atomically.
--
Later,
Jerry.
The Universe is a figment of its own imagination.
No -- parameters are typically passed on the stack, but a dword being
returned will normally be in EAX.
The Interlocked... issue aside there's something that's been somewhat of a mystery to
me: the above *still* is not thread-safe! That's because the AddRef and especially
Release() *themselves* are not synchronized! For example, say you go Release(), which
probably goes something like this:
STDMETHODIMP_(ULONG) CPager::Release()
{
int temp = InterlockedDecrement(&m_dwRef); // 1
if ( !temp ) // 2
delete this; // 3
return temp; // 4
}
OK, now what prevents someone else to do AddRef in 1,2,3, or 4 (or anywhere in
between, as this is C rather than assembly)? Suppose someone does, but if the count
was 0 the object will be deleted! And if it wasn't the returned count will be wrong.
Any comments on this one?
Effectively you're saying:
What if someone releases their *LAST* pointer to an object, but then tries
to get hold of a pointer to it again?
They're not allowed to. There's nothing in C which prevents them doing it
(just as there's nothing in C which prevents you free()ing a block of
memory and then accessing it).
In a language where COM references are more tightly integrated into the
language, I suppose what you ask would be technically impossible. (e.g.
the language calls Release() precisely when the variable goes out of
scope).
--
Lucian Wischik, Queens' College, Cambridge CB3 9ET. www.wischik.com/lu
I think this situation is,
What if someone releases their *LAST* pointer to an object, at the same time
as someone else tries to AddRef() the same object?
Answer: I don't personally know.
> They're not allowed to.
Oh, OK.
josh <jo...@xtreme.net> wrote in message news:<1103_1014481285@oiiwejg-s2-34sg>...
I just realised that this problem can't (or shouldn't) occur.
If two threads each have their own copy of an object then the reference
count will be at least two. Therefore the object will only be freed after
each thread has called Release(). So this situation is illegal: it would be
illegal to call AddRef() while Release() is freeing the object, because that
implies that the thread calling AddRef() is doing so after it has called
Release(). It would mean that there was an unbalanced AddRef()/Release()
pair somewhere.
Its not just "their" last pointer to an object, but "the" last pointer.
(thats why the reference count was just 1). Therefore, since no one else
has a valid pointer to it, no one else has any business trying to addref
it.
The first thread contains the last reference to the object in the entire
universe. (by your assumption that the reference count was initially 1).
Therefore, the second thread does not have a valid pointer to the object,
and has no business trying to AddRef it.
Strictly speaking... they might be fully balanced, but not fully *nested*.
e.g.
Thread1 Thread2
x = CreateObject();
x->AddRef; x = ReceiveFromThread();
PostMessageToThread(x); x->AddRef();
Release(); x->Release();
Here they're both nested. But the problem that we've been discussing still
arises, because the protocol for how you treat references has not been
respected.
JC
"Jerry Coffin" <jco...@taeus.com> wrote in message
news:MPG.16e0afb2f...@news.direcpc.com...
Yes -- when a context switch is done (whether between processes,
threads or fibers) the registers are preserved across the switch. In
fact, registers are _more_ protected from other threads than the
stack is. All threads have access to the memory for other threads'
stacks but simply don't know where in memory they are. It's much
more difficult to get access to a thread's register values --
possible via GetThreadContext and SetThreadContext, but definitely
non-trivial.