Ross Levis wrote:
> Im trying to avoid using a critical section due to time overhead. In one
> thread I have a global integer incrementing roughly every 20 milliseconds
> using Inc(x,y). In another thread I have the same integer decreasing
> using
> Dec(x,y) at about the same speed.
>
> Is it critical to have these in a critical section?
Absolutely! This is a classic race condition. It's probably *exactly* the
example used to introduce the dangers inherent in multithreading.
It's possible to get by without a critical section, but you still need to
take special measures to protect the variable somehow. See the function
below.
> Ive been testing it
> here for a few days continuously and it appears to work fine without one.
Testing doesn't ensure that you've exercised every possible code path.
> Even with a dual processor and each thread running on a separate
> processor,
> is it possible for one memory location to be written precisely at the same
> time? It doesnt matter which occurs first or in which order.
But does it matter that they both occur? It's possible for one operation
to completely cover the other.
Incrementing a value in memory is a three-step process:
1. Load the value from memory into a register
2. Increment the register value
3. Store the register's value back into memory
So here's the scenario where things go wrong:
Thread P does step 1. Now there's a context switch and thread Q takes
over. Thread Q does step 1. At this point, thread Q has the same original
value that thread P has.
We don't even have to continue the scenario to complete the increment and
decrement operations. No matter which thread wins the race -- no matter
what order the remaining four steps occur in -- we're going to have the
wrong value stored into memory. One of the operations will end up having
no effect.
Here's a function that will increment a value properly. It uses a loop
instead of locking the variable. In the loop, it reads the current value
and then computes what the new value should be. Then, it uses
InterlockedCompareExchange to possibly update the value. If the variable
still has the same value it had when we started, then it updates the
variable to hold the new value. Otherwise, it does nothing. If it updated
the variable, then its return value will be equal to the original value
and the loop stops. Otherwise, the loop goes again, this time using the
updated value of the variable since it changed somewhere else.
Since this relies on InterlockedCompareExchange, it takes on that
function's requirements, namely that Value needs to be aligned properly.
That shouldn't be a problem unless you're incrementing a field of a packed
record.
function InterlockedAdd(var Value: Integer; const Delta: Integer): Integer;
var
OriginalVal: Integer;
begin
repeat
OriginalVal := Value;
Result := OriginalVal + Delta;
until OriginalValue = InterlockedCompareExchange(Value, Result,
OriginalVal);
end;
You can write your own InterlockedSubtract if you want.
--
Rob
_______________________________________________
Delphi mailing list -> [email protected]
http://www.elists.org/mailman/listinfo/delphi