Jump to content
  • Advertisement
Sign in to follow this  
Narf the Mouse

Getting a feel for threading - Perplexing problem

This topic is 3051 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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);
        }

Share this post


Link to post
Share on other sites
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 a
push 1 into register b
add a to b
write 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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
#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);

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!