32

Let's suppose I have a thread which should perform some task periodically but this period is 6 times each hour 12 times each hour (every 5 minutes), I've often seen code which controls the thread loop with a is_running flag which is checked every loop, like this:

std::atomic<bool> is_running;

void start()
{
    is_running.store(true);
    std::thread { thread_function }.detach();
}

void stop()
{
    is_running.store(false);
}

void thread_function()
{
    using namespace std::literals;
    while (is_running.load())
    {
        // do some task...
        std::this_thread::sleep_for(5min);
    }
}

But if the stop() function is called, let's say, 1 millisecond after start() the thread would be alive for 299999 additional milliseconds until it awakes, checks the flag, and die.

Is my understanding correct? How to avoid keeping alive (but sleeping) a thread which should have been ended? My best approach until now is the following:

void thread_function()
{
    using namespace std::literals;
    while (is_running.load())
    {
        // do some task...
        for (unsigned int b = 0u, e = 1500u; is_running.load() && (b != e); ++b)
        {
            // 1500 * 200 = 300000ms = 5min
            std::this_thread::sleep_for(200ms);
        }
    }
}

Is there a less-dirty and more straightforward way to achieve this?

PaperBirdMaster
  • 12,806
  • 9
  • 48
  • 94
  • 1
    _6 times each hour (every 5 minutes)_, well 12 times each hours or every 10 minutes? :) – Alexandre Lavoie Apr 21 '15 at 14:34
  • @AlexandreLavoie what a fail! Thanks, I'll correct it! :) – PaperBirdMaster Apr 21 '15 at 14:39
  • 3
    http://en.cppreference.com/w/cpp/thread/condition_variable, see first sentence there. Instead of sleeping for a fixed amount of time, you enter a signallable wait state for that amount of time so other threads can still interrupt you – stijn Apr 21 '15 at 14:40
  • Another option is [boost::basic_waitable_timer](http://www.boost.org/doc/libs/1_49_0/doc/html/boost_asio/reference/basic_waitable_timer.html). – tenfour Apr 21 '15 at 14:46
  • Should your thread do every 5 minutes some work or the first time after 5 minutes and then sleep another 5 minutes after he finished his work? If the first is your case then I would (on windows) create a timer which creates every 5 minutes a thread which does some work. When creating the thread is too much overhead I would use the threadpool. – user743414 Apr 22 '15 at 14:25

3 Answers3

37

Use a condition variable. You wait on the condition variable or 5 minutes passing. Remember to check for spurious wakeups.

cppreference

I cannot find a good stack overflow post on how to use a condition variable in a minute or two of google searching. The tricky part is realizing that the wait can wake up with neither 5 minutes passing, nor a signal being sent. The cleanest way to handle this is to use the wait methods with a lambda that double-checks that the wakeup was a "good" one.

here is some sample code over at cppreference that uses wait_until with a lambda. (wait_for with a lambda is equivalent to wait_until with a lambda). I modified it slightly.

Here is an version:

struct timer_killer {
  // returns false if killed:
  template<class R, class P>
  bool wait_for( std::chrono::duration<R,P> const& time ) const {
    std::unique_lock<std::mutex> lock(m);
    return !cv.wait_for(lock, time, [&]{return terminate;});
  }
  void kill() {
    std::unique_lock<std::mutex> lock(m);
    terminate=true; // should be modified inside mutex lock
    cv.notify_all(); // it is safe, and *sometimes* optimal, to do this outside the lock
  }
  // I like to explicitly delete/default special member functions:
  timer_killer() = default;
  timer_killer(timer_killer&&)=delete;
  timer_killer(timer_killer const&)=delete;
  timer_killer& operator=(timer_killer&&)=delete;
  timer_killer& operator=(timer_killer const&)=delete;
private:
  mutable std::condition_variable cv;
  mutable std::mutex m;
  bool terminate = false;
};

live example.

You create a timer_killer in a shared spot. Client threads can wait_for( time ). If it returns false, it means you where killed before your wait was complete.

The controlling thread just calls kill() and everyone doing a wait_for gets a false return.

Note that there is some contention (locking of the mutex), so this isn't suitable for infinite threads (but few things are). Consider using a scheduler if you need to have an unbounded number of tasks that are run with arbitrary delays instead of a full thread per delayed repeating task -- each real thread is upwards of a megabyte of system address space used (just for the stack).

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Should 'std::unique_lock' be 'std::lock_guard' in kill method? – user1633272 Sep 25 '18 at 07:19
  • @user Why? I guess, but the benefit is small if detectible. – Yakk - Adam Nevraumont Sep 25 '18 at 12:02
  • If you don't call lock.lock(), it doesn't do anything I think. – user1633272 Sep 25 '18 at 13:19
  • @user1633272 No, unique lock is just a movable lock guard. You can create an unlocked unique lock with a mutex, but that isn't the default way they are created. – Yakk - Adam Nevraumont Sep 25 '18 at 13:52
  • The condition variable `cv` should also be declared as `mutable`, not only the mutex `m`. – Kai Petzke Jan 04 '19 at 13:16
  • 1
    @kaip fixed, also modified kill to take into account modern compiler optimization of cv. – Yakk - Adam Nevraumont Jan 04 '19 at 13:56
  • if it's safe to call `cv.notify_all()` like you indicated in your answer why don't you surround the first 2 lines in `kill()` in inner scope so lock will unlock before `cv.notify_all()` ? – SubMachine Dec 19 '19 at 10:38
  • @SubMachine Because modern compilers generate optimal code if you do it this way, reportedly. Basically they defer the "actual" notification to the point where the mutex releases. For a short period, compilers generated faster code if you unlocked the mutex before notifying, but as they matured the other path became better. So I have started doing that. I have not personally profiled the difference; I am relying on people far more familiar with clang and other compiler internals who told me it was faster. – Yakk - Adam Nevraumont May 21 '20 at 12:48
  • Isn't the `unique_lock` defined in `kill` redundant? – Tobi Akinyemi Apr 18 '22 at 13:33
  • @TobiAkinyemi No; as a rule of thumb, you MUST hold the mutex associated with the condition variable in some interval between modifying a cv-mutex associated state and signaling using the cv. There is a race condition otherwise that can lead to the signal being missed. In this case, `terminate` is also a non-atomic; reading it in one thread and writing it in another is a race condition unless synchronized (but making it atomic wouldn't fix the other problem). So it is needed for 2 reasons, both of which are sufficient to require that mutex. – Yakk - Adam Nevraumont Apr 18 '22 at 15:25
  • @Yakk-AdamNevraumont Sorry was confused about `unique_lock` vs `scoped_lock` vs etc. I thought `unique_lock` didn't use the RAII pattern and you had to manually call un/lock - which is why I thought it was redundant since you didn't call those methods. nevermind – Tobi Akinyemi Apr 19 '22 at 11:18
4

There are two traditional ways you could do this.

You could use a timed wait on a condition variable, and have the other thread signal your periodic thread to wake up and die when it's time.

Alternately you could poll on a pipe with your sleep as a timeout instead of of sleeping. Then you just write a byte to the pipe and the thread wakes up and can exit.

Mark B
  • 95,107
  • 10
  • 109
  • 188
-3

yes , by the means of std::mutex , std::lock_guard and std::conditional_variable :

std::mutex mtx;
std::conditional_variable cv;

void someThreadFunction (){
   while(!stopThreadFlag){
     std::lock_gurad<std::mutex> lg(mtx);
     cv.wait_for(lg,SOME_ITERVAL,!stopThreadFlag || wakeTheThreadVariable);
     //the rest here
  }
}
David Haim
  • 25,446
  • 3
  • 44
  • 78