How to let a thread wait itself out without using Sleep()?

2.5k views Asked by At

I want the while loop in the thread to run , wait a second, then run again, so on and so on., but this don't seem to work, how would I fix it?

main(){
    bool flag = true;
    pthread = CreateThread(NULL, 0, ThreadFun, this, 0, &ThreadIP);
    
}
ThreadFun(){
    while(flag == true)
        WaitForSingleObject(pthread,1000); 
}
2

There are 2 answers

1
Phil1970 On BEST ANSWER

While the other answer is a possible way to do it, my answer will mostly answer from a different angle trying to see what could be wrong with your code...

Well, if you don't care to wait up to one second when flag is set to false and you want a delay of at least 1000 ms, then a loop with Sleep could work but you need

  • an atomic variable (for ex. std::atomic)
  • or function (for ex. InterlockedCompareExchange)
  • or a MemoryBarrier
  • or some other mean of synchronisation to check the flag.

Without proper synchronisation, there is no guarantee that the compiler would read the value from memory and not the cache or a register.

Also using Sleep or similar function from a UI thread would also be suspicious.

For a console application, you could wait some time in the main thread if the purpose of you application is really to works for a given duration. But usually, you probably want to wait until processing is completed. In most cases, you should usually wait that threads you have started have completed.

Another problem with Sleep function is that the thread always has to wake up every few seconds even if there is nothing to do. This can be bad if you want to optimize battery usage. However, on the other hand having a relatively long timeout on function that wait on some signal (handle) might make your code a bit more robust against missed wakeup if your code has some bugs in it.

You also need a delay in some cases where you don't really have anything to wait on but you need to pull some data at regular interval.

A large timeout could also be useful as a kind of watch dog timer. For example, if you expect to have something to do and receive nothing for an extended period, you could somehow report a warning so that user could check if something is not working properly.

I highly recommand you to read a book on multithreading like Concurrency in Action before writing multithread code code.

Without proper understanding of multithreading, it is almost 100% certain that anyone code is bugged. You need to properly understand the C++ memory model (https://en.cppreference.com/w/cpp/language/memory_model) to write correct code.

A thread waiting on itself make no sense. When you wait a thread, you are waiting that it has terminated and obviously if it has terminated, then it cannot be executing your code. You main thread should wait for the background thread to terminate.

I also usually recommand to use C++ threading function over the API as they:

  • Make your code portable to other system.
  • Are usually higher level construct (std::async, std::future, std::condition_variable...) than corresponding Win32 API code.
2
Pepijn Kramer On

This is one way to do it, I prefer using condition variables over sleeps since they are more responsive and std::async over std::thread (mainly because std::async returns a future which can send information back the the starting thread. Even if that feature is not used in this example).

#include <iostream>
#include <chrono>
#include <future>
#include <condition_variable>

// A very useful primitive to communicate between threads is the condition_variable
// despite its name it isn't a variable perse. It is more of an interthread signal
// saying, hey wake up thread something may have changed that's interesting to you.
// They come with some conditions of their own
// - always use with a lock
// - never wait without a predicate
// (https://www.modernescpp.com/index.php/c-core-guidelines-be-aware-of-the-traps-of-condition-variables)
// - have some state to observe (in this case just a bool)
// 
// Since these three things go together I usually pack them in a class
// in this case signal_t which will be used to let thread signal each other

class signal_t
{
public:
    // wait for boolean to become true, or until a certain time period has passed
    // then return the value of the boolean.
    bool wait_for(const std::chrono::steady_clock::duration& duration)
    {
        std::unique_lock<std::mutex> lock{ m_mtx };
        m_cv.wait_for(lock, duration, [&] { return m_signal; });
        return m_signal;
    }

    // wiat until the boolean becomes true, wait infinitely long if needed
    void wait()
    {
        std::unique_lock<std::mutex> lock{ m_mtx };
        m_cv.wait(lock, [&] {return m_signal; });
    }
    
    // set the signal
    void set()
    {
        std::unique_lock<std::mutex> lock{ m_mtx };
        m_signal = true;
        m_cv.notify_all();
    }

private:
    bool m_signal { false };
    std::mutex m_mtx;
    std::condition_variable m_cv;
};

int main()
{
    // create two signals to let mainthread and loopthread communicate
    signal_t started; // indicates that loop has really started
    signal_t stop;    // lets mainthread communicate a stop signal to the loop thread.

    // in this example I use a lambda to implement the loop
    auto future = std::async(std::launch::async, [&]
    {
        // signal this thread has been scheduled and has started.
        started.set();

        do
        {
            std::cout << ".";

            // the stop_wait_for will either wait 500 ms and return false
            // or stop immediately when stop signal is set and then return true
            // the wait with condition variables is much more responsive
            // then implementing a loop with sleep (which will only
            // check stop condition every 500ms)
        } while (!stop.wait_for(std::chrono::milliseconds(500)));
    });

    // wait for loop to have started
    started.wait();
    
    // give the thread some time to run
    std::this_thread::sleep_for(std::chrono::seconds(3));

    // then signal the loop to stop
    stop.set();

    // synchronize with thread stop
    future.get();

    return 0;
}