Odd Sleep() Within Thread Behavior

This is a discussion on Odd Sleep() Within Thread Behavior within the Windows Programming forums, part of the Platform Specific Boards category; I have a thread that I use to update information 50 times a second. To make sure it updates at ...

  1. #1
    Registered User
    Join Date
    Mar 2007
    Posts
    416

    Odd Sleep() Within Thread Behavior

    I have a thread that I use to update information 50 times a second. To make sure it updates at that I measure how long it took to update the info, and then subtract that from 20 (1/50 = 20 milliseconds) to get the time remaining that I need to sleep for. If it took longer than 20 milliseconds to update, it does not sleep at all. The problem comes in that every so often the application will sleep longer than it should, usually around 30-32 milliseconds, and this throws off the updating. Even if I remove all updating calculations the sleep function still sleeps longer than it should. I have been using GetSystemTime() to get the milliseconds, and it has worked as it should. Below is some pseudo-code of what my thread looks like. I have tried using CreateThread() and _beginthread() but it doesn't change anything (I didn't expect it to but worth a try). Looking at the task manager under both the correct sleep time, and the 30+ms sleep time the cpu percentage and memory usage are the same, if it makes a difference.

    Code:
    void __stdcall mythread(void* param)
    {
        while(true)
        {
            GetSystemTime(&starttime);
            // do updating info, but this is commented out and it still sleeps too long
            GetSystemTime(&endtime);
            GetSystemTime(&starttime2);
            if (endtime - starttime < 20 milliseconds)
            {
                Sleep(20 - (endtime - starttime));
            }
            GetSystemTime(&endtime2);
            // here is where endtime2 - starttime2 reports being more than 30ms
    }

  2. #2
    and the hat of wrongness Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    32,452
    Sleep Function (Windows)
    Quote Originally Posted by msdn
    Note that a ready thread is not guaranteed to run immediately. Consequently, the thread may not run until some time after the sleep interval elapses. For more information, see Scheduling Priorities.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.
    I support http://www.ukip.org/ as the first necessary step to a free Europe.

  3. #3
    Banned
    Join Date
    Aug 2010
    Location
    Ontario Canada
    Posts
    9,547
    Salem has it...

    When you call Sleep() you are giving up your timeshare and allowing other code to run. Your next timeshare comes around when the circle completes itself... And that may not be for quite a while, when you're working in milliseconds.

    A better technique might be to use a timer with a callback to your code. These are not pinpoint accurate either (+ - about 2ms) but they're going to be more accurate than Sleep()ing.

    There are also techniques using GetTickCount() --a 1 millisecond heartbeat-- that should get you within a millisecond. Something like if(! GetTickCount() % 20) might be just what you need...
    Last edited by CommonTater; 02-11-2011 at 08:23 AM.

  4. #4
    Registered User
    Join Date
    Mar 2007
    Posts
    416
    Thank you very much. I will look in to GetTickCount(), and other ways to sleep for 1ms at a time.

  5. #5
    and the hat of wrongness Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    32,452
    Personally, I think making the code adapt to variable delays would be better

    Code:
    do {
        GetSystemTime(&new);
        doWork(new-old);
        GetSystemTime(&old);
        Sleep(20-(old-new));  // or whatever
    }
    Rather than assuming that it will always be
    doWork(20);
    and coming up with various schemes to try and turn windows into an RTOS.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.
    I support http://www.ukip.org/ as the first necessary step to a free Europe.

  6. #6
    Banned
    Join Date
    Aug 2010
    Location
    Ontario Canada
    Posts
    9,547
    Quote Originally Posted by Salem View Post
    Personally, I think making the code adapt to variable delays would be better
    This is where the GetTickCount() comes in. If you want to do something exactly every (say) 100 milliseconds, you would use a windows tread where this is the entire thread...
    Code:
    // thread call
    void DoYourThing(void)
      { // whatever  }     
    
    
    // the thread
    BOOL WINAPI TimedProc(LPVOID x);
      { while (!Quit)   // thread bail code
            {  if (!(GetTickCount() %100))   // wait for 0
                   DoYourThing(); 
                 Sleep(0); } }    // give up 1 timeslice (prevent CPU racing)
    
    
    // in mainline code
    BOOL Quit = 0;  // this is global
    DWORD Thread;
    HANDLE hThread;
    hThread = CreateThread(NULL,0,&TimedProc,NULL,0,&Thread);
    If the DoYourThing call takes 10 ms, the loop will idle for 90... it it takes 70, the loop idles for 30... you always end up calling the function on exactly 100ms intervals...

    The only cotasil to this is that if your function takes 105 milliseconds, you will skip a beat, waiting for 95 ms before triggering again. If it always takes 105 milliseconds, it will only fire every other time (i.e. on 200ms intervals). So at short intervals like 20ms, you don't get to do a whole lot.
    Last edited by CommonTater; 02-11-2011 at 11:07 AM.

  7. #7
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,652
    DoYourThing() may never be called at all, except when you're lucky enough to get an even multiple of 100. If you're going to use GetTickCount() at all, calculate a delta. Or better yet:
    Code:
    #include <Windows.h>
    #include <process.h>
    #include <stdio.h>
    
    DWORD TicksElapsed(DWORD start, DWORD end)
    {
        if (end < start)
            return (0xFFFFFFFFUL - start) + end;
        return end - start;
    }
    
    struct PeriodicCall
    {
        HANDLE evExit;
        DWORD period;
        void (*func)();
    };
    
    unsigned __stdcall PeriodicCaller(void *param)
    {
        PeriodicCall *pc = (PeriodicCall*)param;
        DWORD timeout = 0;
    
        for (;;)
        {
            DWORD status = WaitForSingleObject(pc->evExit, timeout);
            if (status == WAIT_TIMEOUT)
            {
                DWORD start = GetTickCount();
                pc->func();
                DWORD elapsed = TicksElapsed(start, GetTickCount());
                if (elapsed > pc->period)
                    timeout = 0;
                else
                    timeout = pc->period - elapsed;
            }
            else
                break;
        }
        return 0;
    }
    
    void foo() {printf("foo: %u\n", GetTickCount());}
    
    int main()
    {
        PeriodicCall pc;
        pc.evExit = CreateEvent(0, TRUE, FALSE, 0);
        pc.func = &foo;
        pc.period = 100;
    
        HANDLE hThread = (HANDLE)_beginthreadex(0, 0, &PeriodicCaller, &pc, 0, 0);
        Sleep(1000);
        SetEvent(pc.evExit);
        WaitForSingleObject(hThread, INFINITE);
        CloseHandle(hThread);
        CloseHandle(pc.evExit);
    
        return 0;
    }
    gg

  8. #8
    Banned
    Join Date
    Aug 2010
    Location
    Ontario Canada
    Posts
    9,547
    Yep, another good way to do it...

    For the "lucky enough to get a 0 value"... the extremely tight loop in my example probably calls GetTickCount() about 10 to 20 times per millisecond... It hasn't missed yet, except as I've described...

    Well, unless you're still back in the good ole 386 days that is

  9. #9
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,652
    Surely you're not defending your non-deterministic implementation. Yes, when DoYourThing() takes less then a ms it's more often "lucky" than not.

    Sleep(0) can be an ineffective way to yield the CPU - https://blogs.msdn.com/b/oldnewthing...04/476847.aspx
    And this may be a rare case where increasing thread priority is actually warranted for a more consistent frequency of calls, despite what the rest of the system wants to do. (But as Salem mentioned, Windows is not an RTOS so no guarantees.)

    There's also unsynchronized access to the shared variable 'Quit'. A multi-threading no-no, despite any correct behavior that your compiler/platform may produce. Under Posix and C++0x, it's "ill-formed, undefined behavior".

    gg

  10. #10
    Banned
    Join Date
    Aug 2010
    Location
    Ontario Canada
    Posts
    9,547
    Quote Originally Posted by Codeplug View Post
    Surely you're not defending your non-deterministic implementation. Yes, when DoYourThing() takes less then a ms it's more often "lucky" than not.
    It does not have to return within a millisecond... it needs to return before the next occurance of

    GetTickCount() % delay == 0

    If you are using 100000 as your delay... it has 100 seconds - 1 tick to get back to the loop.
    Sleep(0) invokes no protracted delay. It merely allows the system to multitask without racing the CPU.

    There's also unsynchronized access to the shared variable 'Quit'. A multi-threading no-no, despite any correct behavior that your compiler/platform may produce. Under Posix and C++0x, it's "ill-formed, undefined behavior".
    Setting Quit to a non-zero value is the bail signal for the thread... the loop will exit and the thread will terminate... It's there to put out a fire, nothing more.

    And yes I've used it
    And yes it works.

  11. #11
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,652
    Hmmm, you are failing to see how horribly broken and wrong your method is...
    Code:
    #include <Windows.h>
    #include <stdio.h>
    
    const DWORD period = 100;
    DWORD numFooCalls = 0;
    
    DWORD TicksElapsed(DWORD start, DWORD end)
    {
        if (end < start)
            return (0xFFFFFFFFUL - start) + end;
        return end - start;
    }
    
    DWORD foo() 
    {
        static DWORD lastCall = 0;
        DWORD curCall = GetTickCount();
        DWORD callsMissed = 0;
        if (lastCall)
        {
            DWORD elapsed = TicksElapsed(lastCall, curCall);
            if (elapsed > (period * 2))
            {
                callsMissed = (elapsed / period) - 1;
                printf("Missed: last=%u, now=%u, diff=%u, #missed=%u\n",
                       lastCall, curCall, elapsed, callsMissed);
            }
        }
        lastCall = curCall;
        Sleep(10); // change this however you like...
        ++numFooCalls;
        return callsMissed;
    }
    
    int main()
    {
        const DWORD missedCallsBeforeExit = 10;
        DWORD numMissed = 0;
        foo();
        while (numMissed < missedCallsBeforeExit)
        {
            // may take a while since we have to get lucky just to call foo() again
            if (GetTickCount() % period == 0)
                numMissed += foo();
        }
    
        printf("Number of calls = %u\n", numFooCalls);
        return 0;
    }
    >> ... It merely allows the system to multitask without racing the CPU.
    I provided a link in post #9, describing how and why it can in fact race the CPU.

    >> And yes I've used it. And yes it works.
    It's completely irrelevant if you've used it or if it's worked for you in the past. What is relevant is that it's undefined behavior according to any C/C++ standards that include multi-threading.

    gg

  12. #12
    Banned
    Join Date
    Aug 2010
    Location
    Ontario Canada
    Posts
    9,547
    Ok, then... I'll never use it again...

    Gees...

  13. #13
    train spotter
    Join Date
    Aug 2001
    Location
    near a computer
    Posts
    3,856
    Quote Originally Posted by CommonTater View Post
    A better technique might be to use a timer with a callback to your code. These are not pinpoint accurate either (+ - about 2ms) but they're going to be more accurate than Sleep()ing.
    WM_TIMER msgs are THE lowest priority in the OS msg queue, after WM_PAINT.

    Getting a consistent resolution of +/- 2 msec is impossible, getting +/- 50 msec is hard in any system under load.

    Quote Originally Posted by CommonTater View Post
    There are also techniques using GetTickCount() --a 1 millisecond heartbeat-- that should get you within a millisecond. Something like if(! GetTickCount() % 20) might be just what you need...
    GetTickCount is a low performance timer using the system timer, which has a minimum resolution of ~10 msec (closer to 15 msec on the sytems I use).

    I found it very difficult to get sub to one millisec timers on MS OS's (except CE) without using a filter driver.

    QueryPerformanceCounter() is much better resolution, but may have issues on threads running on differing cores of a multi core CPU (and if not implemented on the system hardware will default to call GetTickCount()).
    Last edited by novacain; 02-13-2011 at 12:16 AM.
    "Man alone suffers so excruciatingly in the world that he was compelled to invent laughter."
    Friedrich Nietzsche

    "I spent a lot of my money on booze, birds and fast cars......the rest I squandered."
    George Best

    "If you are going through hell....keep going."
    Winston Churchill

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. API Thread HEADACHE
    By WaterNut in forum Windows Programming
    Replies: 11
    Last Post: 01-16-2007, 09:10 AM
  2. [code] Win32 Thread Object
    By Codeplug in forum Windows Programming
    Replies: 0
    Last Post: 06-03-2005, 03:55 PM
  3. multithreading question
    By ichijoji in forum C++ Programming
    Replies: 7
    Last Post: 04-12-2005, 10:59 PM
  4. Win32 Thread Object Model Revisted
    By Codeplug in forum Windows Programming
    Replies: 5
    Last Post: 12-15-2004, 07:50 AM
  5. Thread programming behavior
    By Alextrons in forum Windows Programming
    Replies: 3
    Last Post: 10-31-2001, 06:30 AM

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21