I am facing the problem, that I have an C# (.NET) object shared among some threads. A thread might replace the object with another one. The threads are waken up from a TCP/IP connection using the Asynchronous framework.
Threads (waiting for connection) -> Asynchronous Callback -> Do something thread-safe -> Access the shared object -> Do something thread-safe.
Object sharedObject = new Object();
Mutex objectMutex = new Mutex();
void threadCallback()
{
Object newObject = new Object();
// some processing
objectMutex.lock();
// do exchange sharedObject with newObject if needed
// very little processing here
objectMutex.unlock();
// some processing
}
Object sharedObject = new Object();
int usingSharedObject = 0;
void threadCallback()
{
Object newObject = new Object();
// some processing
// poll until we lock
while(1 == Interlocked.Exchange(ref usingSharedObject , 1))
{
// do exchange sharedObject with newObject if needed
// very little processing here
Interlocked.Exchange(ref usingSharedObject , 0); // free lock
}
// some processing
}
I expect the second solution to be faster as long as there are not to many threads polling at the same time. The second solution might even sleep a random time so that polling does not eat up any processing time. The first solution looks cleaner to me if I really do need to process a lot of TCP/IP connections. Since I do very little processing in the locked section regarding the TCP/IP processing, will there be any scale-up-issues?
In my C++ background I always used memory-pools in such a situation, since I must use safe .NET is there a fast way to create new Objects or does the .NET platform perform well in this field.
Best regards,
Friedrich
Your interlocked operation is incorrect. A spin-lock usually looks something like this:
int sharedLock = 0;
void callback()
{
do {
int existingState = Interlocked.CompareExchange(ref sharedLock, 1, 0);
if (0 == existingState) {
break;
}
} while (true);
try
{
// operate on the shared state
}
finally
{
int existingState = Interlocked.Exchange(ref sharedLock, 0);
Debug.Assert (1 == existingState);
}
}
As for the reason's to use one vs. the other, it depends primarily on the kind of operations done while the lock is held. Very short operations (short arithmetic add/substract, simple state flag changes etc) are better suited for a spinlock. Heavy operations (allocations, IO access) cannot occur under spinlock so they have to be done under true mutex.
It appears at first glance that your 2 examples might not be equivalent. It looks to me like your polling solution using Interlocked.Exchange()
will drop into the processing loop while something else has claimed the 'homemade semaphore' and will skip over swapping the sharedObject
and newObject
if the homemade semaphore is claimed. (Unless I'm misunderstanding something, which is quite possible).
Since correctness issues are more important that performance issues, and synchronization primitives can be quite tricky to get correct I'd go with the Mutex
solution first and look to another solution if it turned out to be a problem.
Win32 has added a mutex object with a spin count to get the best of both worlds of what you're trying to do here (I think). However, as far as I know it hasn't been exposed in the .NET framework yet.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With