Search code examples
c++cmultithreadingsignalsinterrupt

How to exit sleep() when an interrupt arrives?


I'm looking for a way to exit sleep when an user interrupt arrives. It's important to exit sleep rather than do this: interrupt sleep, do ISR processing, and go back to sleep - which is what I'm finding solutions for.

I'm looking for something like this in C++ - an equivalent in C is even better:

void *timer_thread(void *dummy)
{
  while(1)
  {
    // Check if some callbacks are to be given
    // when all are given, Determine x duration to sleep
  
    try
    {
      sleep(x);
    }
    except(/* the except block should hit ONLY when an interrupt arrives, 
              that too only when sleep() is executed. It's OK to delay 
              interrupt until the sleep is beginning execution */) 
    {
      //Do something else
    }
  }
}

The arriving interrupt will mostly tell that the sleep should be reduced for the timer thread to give callbacks earlier. But irrespective of the use case, I just need the sleep to be exited in some way when interrupt arrives. I just couldn't find info on how to do this.

PS: It's ok to discard/NOP the interrupts if it occurs when sleep wasn't happening

This is on Cygwin gcc v9.3.0 on Windows 10 (C/C++). I don't need the code to be portable, so any platform specific solution is ok as well.

If there's some other solution which is similar to this working (which doesn't use sleep() and interrupts), would be welcome to hear it. I'm just looking for a way which doesn't involve polling.


Solution

  • To wait for a certain time or on a certain event, I would use a combination of std::mutex and std::condition_variable and specifically std::condition_variable::wait_for() to await either time-out or a signaled change of something.

    A minimal sample program for demonstration:

    #include <atomic>
    #include <condition_variable>
    #include <iostream>
    #include <mutex>
    #include <thread>
    #include <chrono>
    using namespace std::chrono_literals;
    
    // a thread-shared flag to signal exit
    std::atomic<bool> exitThread = false;
    
    // a mutex to sync. inter-thread communication
    std::mutex mtxAlert;
    // a condition variable to signal changed data
    std::condition_variable sigAlert;
    // the data of inter-thread communication
    bool alert = false;
    
    void timerThread()
    {
      // the timeout for timer thread
      auto timeout = 100ms;
      // runtime loop
      while (!exitThread) {
        // lock mutex (preparation to wait in cond. var.)
        std::unique_lock<std::mutex> lock(mtxAlert);
        // unlock mutex and wait for timeout or signaled alert
        sigAlert.wait_for(lock, timeout, []() { return alert || exitThread; });
        // mutex is locked again
        // check why wait_for() exited
        if (exitThread) {
          std::cout << "Timer thread exiting...\n";
          return;
        } else if (alert) {
          std::cout << "Timer was interrupted due to alert.\n";
          alert = false;
        } else {
          std::cout << "Timer achieved time-out.\n";
        }
      }
    }
    
    int main()
    {
      std::thread threadWait(&timerThread);
      // wait a bit
      std::cout << "main(): Waiting 300ms...\n";
      std::this_thread::sleep_for(300ms);
      // sim. interrupt
      std::cout << "main(): Sim. interrupt.\n";
      { std::lock_guard<std::mutex> lock(mtxAlert);
        alert = true;
      }
      sigAlert.notify_all();
      // wait a bit
      std::cout << "main(): Waiting 50 ms...\n";
      std::this_thread::sleep_for(50ms);
      // sim. interrupt
      std::cout << "main(): Sim. interrupt.\n";
      { std::lock_guard<std::mutex> lock(mtxAlert);
        alert = true;
      }
      sigAlert.notify_all();
      // wait a bit
      std::cout << "main(): Waiting 50 ms...\n";
      std::this_thread::sleep_for(50ms);
      // exiting application
      exitThread = true;
      sigAlert.notify_all();
      threadWait.join();
      // done
      std::cout << "Done.\n";
    }
    

    Output:

    main(): Waiting 300ms...
    Timer achieved time-out.
    Timer achieved time-out.
    main(): Sim. interrupt.
    main(): Waiting 50 ms...
    Timer was interrupted due to alert.
    main(): Sim. interrupt.
    main(): Waiting 50 ms...
    Timer was interrupted due to alert.
    Timer thread exiting...
    Done.
    

    Live Demo on coliru


    OP claimed per comment that this sample didn't compile properly on cygwin. I tried on my side and can confirm some minor issues which I fixed:

    1. Missing #include <mutex> added

    2. Initialization of std::atomic<bool> exitThread = false; changed to

      std::atomic<bool> exitThread(false);
      

      I got this when I compiled with g++ as well as with g++ -std=c++14. (It seems that -std=c++14 is the default of my g++.)

      When I use g++ -std=c++17 instead I don't get any complaint. I strongly believe that has something to do with copy-elision which g++ applies with -std=c++17 but not prior.

    However, this is my sample session with the slightly reviewed code on my Windows 10 laptop in cygwin64:

    $ g++ --version
    g++ (GCC) 7.4.0
    
    $
    
    $ cat >testCondVar.cc <<'EOF'
    > #include <atomic>
    > #include <condition_variable>
    > #include <iostream>
    > #include <mutex>
    > #include <thread>
    > #include <chrono>
    > using namespace std::chrono_literals;
    > 
    > // a thread-shared flag to signal exit
    > std::atomic<bool> exitThread(false);
    > 
    > // a mutex to sync. inter-thread communication
    > std::mutex mtxAlert;
    > // a condition variable to signal changed data
    > std::condition_variable sigAlert;
    > // the data of inter-thread communication
    > bool alert = false;
    > 
    > void timerThread()
    > {
    >   // the timeout for timer thread
    >   auto timeout = 100ms;
    >   // runtime loop
    >   while (!exitThread) {
    >     // lock mutex (preparation to wait in cond. var.)
    >     std::unique_lock<std::mutex> lock(mtxAlert);
    >     // unlock mutex and wait for timeout or signaled alert
    >     sigAlert.wait_for(lock, timeout, []() { return alert || exitThread; });
    >     // mutex is locked again
    >     // check why wait_for() exited
    >     if (exitThread) {
    >       std::cout << "Timer thread exiting...\n";
    >       return;
    >     } else if (alert) {
    >       std::cout << "Timer was interrupted due to alert.\n";
    >       alert = false;
    >     } else {
    >       std::cout << "Timer achieved time-out.\n";
    >     }
    >   }
    > }
    > 
    > int main()
    > {
    >   std::thread threadWait(&timerThread);
    >   // wait a bit
    >   std::cout << "main(): Waiting 300ms...\n";
    >   std::this_thread::sleep_for(300ms);
    >   // sim. interrupt
    >   std::cout << "main(): Sim. interrupt.\n";
    >   { std::lock_guard<std::mutex> lock(mtxAlert);
    >     alert = true;
    >   }
    >   sigAlert.notify_all();
    >   // wait a bit
    >   std::cout << "main(): Waiting 50 ms...\n";
    >   std::this_thread::sleep_for(50ms);
    >   // sim. interrupt
    >   std::cout << "main(): Sim. interrupt.\n";
    >   { std::lock_guard<std::mutex> lock(mtxAlert);
    >     alert = true;
    >   }
    >   sigAlert.notify_all();
    >   // wait a bit
    >   std::cout << "main(): Waiting 50 ms...\n";
    >   std::this_thread::sleep_for(50ms);
    >   // exiting application
    >   exitThread = true;
    >   sigAlert.notify_all();
    >   threadWait.join();
    >   // done
    >   std::cout << "Done.\n";
    > }
    > EOF
    
    $
    

    Compiled and started:

    $ g++ -std=c++14 -o testCondVar testCondVar.cc
    
    $ ./testCondVar
    main(): Waiting 300ms...
    Timer achieved time-out.
    Timer achieved time-out.
    main(): Sim. interrupt.
    main(): Waiting 50 ms...
    Timer was interrupted due to alert.
    main(): Sim. interrupt.
    main(): Waiting 50 ms...
    Timer was interrupted due to alert.
    Timer thread exiting...
    Done.
    
    $
    

    Note:

    The only reason that at minimum C++14 is required for this sample is the usage of the std::chrono_literals which enables the usage of e.g. 300ms.

    Without std::chrono_literals, this could be written as std::chrono::milliseconds(300) (which is admittedly less convenient). Replacing all std::chrono_literals respectively, I was able to compile and run the sample with -std=c++11 as well.