Getting a feel for threading - Perplexing problem

Started by
14 comments, last by Narf the Mouse 14 years, 2 months ago
To get a feel for threading, I decided to make a simple action/display set-up - One thread would count, the other would display the count. The main thread would wait until a key was pressed, then end. It works almost perfectly - For some reason, it pauses every few seconds. I'm wondering why. Thanks for any and all help.

        static void Main(string[] args)
        {
            System.Threading.Thread threadCount = new System.Threading.Thread(Count);
            System.Threading.Thread threadDisplay = new System.Threading.Thread(Display);
            threadCount.IsBackground = true;
            threadDisplay.IsBackground = true;
            threadDisplay.Start();
            threadCount.Start();
            

            Console.ReadKey(true);
        }


        static int n = 0;


        static void Count()
        {
            while (1 == 1)
                n = n + 1;
        }


        static void Display()
        {
            while (1 == 1)
                Console.WriteLine(n);
        }

Advertisement
WriteLine and 'n=n+1' are not guaranteed to be atomic operations. So what happens is the following:
load n from memory into register apush 1 into register badd a to bwrite result into memory of n

These are technically 4 operations, and WriteLine can execute many times in between. Even if such operation is implemented atomically, there is no synchronization between two threads, meaning they each run independently.

Due to too many reasons, WriteLine can execute hundreds, even thousands of times while the above code is executing, seeing no change at all. On multiple cores, without n being volatile, each core might not write the value back into memory/cache for other core to be able to use it.

And on single core or HT machines, each of the busy loops can dominate the CPU time, starving the other thread.

At minimum, n must be declared as volatile, and adding a Thread.sleep(0 or 1) into the busy loops would help balance the load.

This type of constructs are generally undesirable though, there are considerably better ways to communicate between threads.
Ah - So first, store a copy for WriteLine of the last thing it wrote and only execute if n != copy?

Thanks - What would be a good tutorial on those constructs?

Also, .Net's Console is very slow - Count executes a lot more than Display.

Edit: Adding Thread.Sleep(1); to Display appears to have fixed the problem.
Quote:Original post by Narf the Mouse

Edit: Adding Thread.Sleep(1); to Display appears to have fixed the problem.


Unless you declare the shared variable volatile, you haven't solved anything, just hid the problem on your CPU on your CLR for this test. It is the count() that is problematic.

You need to add sleep to both threads, or it will likely break on single core or HT CPUs, where increment thread will take over all the CPU. Run your test, set processor affinity to single core, and the problem will likely manifest itself.
I did indeed set it to volatile.

That makes sense. Putting them both on Thread.Sleep(0) also solves the problem. It also slows it down, but I guess a maximum of a million executions a second is enough for most purposes. :D
you realy shoud look after the voilatile and atomic solution you will definetly need it later

"You need to add sleep to both threads, or it will likely break on single core or HT CPUs"


why ?

unless you give it a higher priority it will use its timeslice and get to the and of the quee
#include <intrin.h>
#pragma intrinsic(_InterlockedIncrement)
volatile long n;
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
while(1)
{
if(lpParameter)
{
wchar_t str[1000];
wsprintf(&str[0], L"Num: %d \n", n);
OutputDebugString(&str[0]);
}
else
{
_InterlockedIncrement(&n);
}
};
return 0;
};


int WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR, int)
{
CreateThread(0, 0, &ThreadProc, 0, 0, 0);
CreateThread(0, 0, &ThreadProc, (void*)1, 0, 0);
Quote:Original post by Discard_One
#include <intrin.h>
#pragma intrinsic(_InterlockedIncrement)
volatile long n;


What exactly does locked increment do here that isn't covered by volatile already (note the OP has C# code)?

Given there is a one reader+writer and one passive observer, what additional safety would interlocked operations bring? Observer cannot read inconsistent value, and there is no synchronization.

Quote:why ?

unless you give it a higher priority it will use its timeslice and get to the and of the quee


Because busy wait loops don't work well with the scheduler - original example is effectively a spin-wait. It will not break, but is very likely to cause scheduling degradation.

Same caution must be taken when designing lock-less algorithms with multiple readers and/or multiple writers. This is much less of a concern these days due to prevalence of multi-core.
Quote:
isn't covered by volatile already (note the OP has C# code)

Unless I read the article wrong, C# "volatile" only insures that all threads reading get the latest value, and any write will instantly become the correct value in memory.(memory being the cache)

This confirms
So,
n = n + 1
running in two threads A could read "5". B then reads "5". A increments and stores "6". B increments and stores "6". When n should have been "7".
Volatile is not a replacement for lock.
the interlockincrment insures that n takes the value of 7 in the above example.

BUT as you said, there is only one writer. and one reader.
So, really the only problem is that the writer can update n several times before the reader reads n. But, that might not be a problem, so there might not be a need to actually lock increment.
here nothing

but if he looks it up he will find the other interloked funcs


EDIT:

we have just 1 thread incrementing in my example but yeh if an other comes to it we coud get problems


EDIT2:

by the way i expect increment to by faster then x += 1 , x++ or similair

This topic is closed to new replies.

Advertisement