Search code examples
c++multithreadingsignals

How to ensure signal raised by sub thread is handled by main thread?


My code runs with multi-threads. In the meantime, there is a signal_handler function which handle the external ctrl-c signal.

I found the signal_handler function runs on the thread which raise the signal.

But i want to handle it on main thread no matter who raise it.

here is a demo code:

#include <csignal>
#include <iostream>
#include <thread>

std::thread::id main_id;

void signal_handler(int sig) {
  // here always run in subthread, I want to make it run in main thread 
  std::thread::id this_id = std::this_thread::get_id();
  std::cout << this_id << " got signal\n";
  signal(sig, SIG_DFL);
  exit(sig);
}

void thread_fn() { 
  while (1) { 
    std::cout << "subthread " << std::this_thread::get_id() << " printing\n";
    std::this_thread::sleep_for(std::chrono::seconds(5));
    raise(SIGINT);
  } 
}

int main() { 
  signal(SIGINT, signal_handler);
  std::thread t(&thread_fn);

  main_id = std::this_thread::get_id();
  while (1) { 
    std::cout << "main " << main_id << " printing\n";
    std::this_thread::sleep_for(std::chrono::seconds(5));
  } 

  t.join();
}

could you help on this? I am very stingy which means i dont want to create a single thread to wait thread.


Solution

  • POSIX with pthreads (Linux, FreeBSD, macOS, etc.)

    Technically signals are delivered to individual threads and not the process itself. In order to invoke a signal handler on another thread, you need to use pthread_kill() and supply the id of the thread where the signal handler should be executed.

    So if I modify your code just a tiny bit to use the low-level functions (because there's no std::thread based wrapper around pthread_kill()):

    #include <csignal>
    #include <iostream>
    #include <thread>
    
    #include <pthread.h>
    
    pthread_t main_thread_raw_id;
    
    void signal_handler(int sig)
    {
      // ... [see comments below]
    }
    
    void thread_fn() { 
      while (1) { 
        std::cout << "subthread " << std::this_thread::get_id() << " printing\n";
        std::this_thread::sleep_for(std::chrono::seconds(5));
        pthread_kill(main_thread_raw_id, SIGINT);
      } 
    }
    
    int main() { 
      signal(SIGINT, signal_handler);
      std::thread t(&thread_fn);
    
      main_thread_raw_id = pthread_self();
      auto main_id = std::this_thread::get_id();
      while (1) { 
        std::cout << "main " << main_id << " printing\n";
        std::this_thread::sleep_for(std::chrono::seconds(5));
      } 
    
      t.join();
    }
    

    Then the main thread will get delivered the signal. (Note that both std::this_thread::get_id() and pthread_self() are considered opaque and there is no guarantee that they are equal to each other, even if they might be on your system.)

    That all said:

    As others have pointed out, using cout from within a signal handler is not safe and can lead to all sorts of issues. This applies to many functions you might use, starting from everything that allocates on the heap (via new or malloc()), up to mutexes, etc. Even exit() is not safe, only _exit() is, because exit() may call destructors registered with atexit() (and also C++ destructors), while _exit() skips those.

    All of these restrictions make signal handlers very hard to write correctly, and most people who use them just set an atomic flag or something similar, so that it's easy to reason that the handler itself is safe.

    Going even further, unless it's some very specific signals such as SIGTSTP where that isn't feasible in practice, many modern software on Linux doesn't even want to use signal handlers, but rather register a signalfd() with an event loop.

    Windows

    On Windows, you're completely out of luck here: Microsoft implements signal() only because the C standard requires them to - and they implement it differently:

    • SIGTERM/SIGILL is never generated
    • Whenever a condition for a signal is generated through a code issue, the signal handler is executed within that thread itself.
    • When using abort() or raise(signal), that thread will execute the signal handler.
    • When pressing Ctrl+C in a console to invoke SIGINT, a new thread will be created on Windows that invokes the signal handler.

    Basically, any program that uses signal() only to the extent that the C standard defines it, will work with Microsoft's implementation thereof, but anything that relies on actual POSIX semantics will not. Windows provides a ton of other mechanisms for interacting with threads (from APCs to suspending and directly modifying the thread's execution state) where you could probably emulate the behavior in some manner, but Windows decidedly does not implement POSIX thread semantics.

    See the comments in this question for more details here.

    Concluding Remarks

    You appear to want to use signals as some kind of thread synchronization mechanism here. And while that is certainly possible, and in some edge cases it might even be useful (e.g. when real-time threads are involved), I would strongly recommend against using signal handlers for this purpose.

    What is the actual problem you are trying to solve here?