vendredi 28 septembre 2018

Is a condition variable's .wait_for() an efficient way to perform a background task at a specific interval?

I need to run an activity every so often while my program is running. In production code this is configurable with a default of 30 minutes, but in the example below I've used 5 seconds. Previously I had a std::thread that would loop once per second checking to see if it was time to run the activity OR if the program was closed. This allowed me to close the program at any time without having the .join() on the activity's thread block my application's exit waiting for its next iteration. At any moment it was less than a second away from checking to see if it should close or perform the activity.

I do not like the idea of wasting time checking every second for an activity that may only occur every 30 minutes while the program is running, so I attempted to switch it to a condition variable. I've included a small example of my implementation below. I want to be sure I'm using the right tools to do this. The issue I see with my code is unnecessary calls of the lambda expression which I'll explain below.

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

bool asking_thread_to_quit;
std::mutex cv_mutex;
std::condition_variable cv;

void RunThread()
{
    asking_thread_to_quit = false;

    std::cout << "Started RunThread." << std::endl;
    while(true)
    {
        std::unique_lock<std::mutex> lock(cv_mutex);
        std::chrono::seconds delay(5);
        if(cv.wait_for(lock, delay, [] { std::cout << "WAKEUP" << std::endl; return asking_thread_to_quit; })) // timed out
        {
            std::cout << "Breaking RunThread Loop." << std::endl;
            break;
        }
        else
            std::cout << "TIMER CODE!" << std::endl;
    }
}

int main(int argc, char *argv[])
{
    std::cout << "Program Started" << std::endl;
    std::thread run_thread(RunThread);

    // This is where the rest of the program would be implemented, but for the sake of this example, simply wait for user input to allow the thread to run in the background:
    char test;
    std::cin >> test;

    asking_thread_to_quit = true;
    cv.notify_all();

    std::cout << "Joining RunThread..." << std::endl;
    run_thread.join();
    std::cout << "RunThread Joined." << std::endl;

    return 0;
}

If you execute the program and allow for one 5-second iteration to pass, it gives the following output:

Program Started
Started RunThread.
WAKEUP
WAKEUP
TIMER CODE!
WAKEUP
q    <-- I typed this to quit.
Joining RunThread...
WAKEUP
Breaking RunThread Loop.
RunThread Joined.

You can see that it does the following:

  1. (WAKEUP) Performs the check prior to waiting

  2. Wait for five seconds

  3. (WAKEUP) Performs the check

  4. (TIMER CODE!) Executes the activity
  5. (WAKEUP) Performs the check again before going back to waiting

Step 5 seems unnecessary as I just performed it a split second ago, but I believe it is necessary as .wait_for() doesn't know I'm using it inside of a while(true) loop. Is this something I'm stuck with, or is there a way to remove the initial check in the .wait_for() call? I'm guessing there is not as it would allow for the system to .wait_for() something that it doesn't need to wait for. This is what leads me to wonder if I'm using the right language features to begin with. Is there a better way?

Aucun commentaire:

Enregistrer un commentaire