-1

I've been replacing an existing framework with C++17 and must leave the existing logic in place but replace the framework with standard C++.

I need an is_running() method for std::thread. My first idea was to use an std::atomic and compare_exchange_strong() inside the wrapper class' thread function that calls the passed in thread function. This way I'd at least know when the passed in function returned.

One requirement is that I can't change the logic even for a bug so, other C++ threading methods won't be acceptable.

James Smith
  • 817
  • 1
  • 4
  • 13
  • What have you tried? What specific problems have you encountered? Stack Overflow is not a "write my code for me" service. – Andrew Henle Jun 26 '23 at 23:44
  • @Andrew Henie It's joinable until std::thread::join() returns even if the thread function is finished. – James Smith Jun 26 '23 at 23:48
  • How can you trust this `is_running` method? You test, the state changes post-test, you take actions based on the previously-true state. – user4581301 Jun 26 '23 at 23:49
  • @user4581301 Not sure but offhand I'm thinking if you call the passed in thread function inside a wrapper, settng an atomic when passed in function return would tell me when a call to join() won't block. – James Smith Jun 26 '23 at 23:55
  • 2
    [There are a couple neat tricks here](https://stackoverflow.com/q/9094422/4581301), one of which you're already considering. – user4581301 Jun 26 '23 at 23:56
  • Sounds like your case is the simpler "I don't wanna join and wait" rather than the "I don't want to start thread if it's already started." You can probably get away with the atomic bool or flag – user4581301 Jun 27 '23 at 00:02

2 Answers2

1

Since you need to be backward compatible you could do something like this.

#include <chrono>
#include <iostream>
#include <thread>
#include <memory>

using namespace std::chrono_literals;

// make a wrapper around thread
// that can r
class my_thread_t
{
public:
    ~my_thread_t()
    {
        m_thread->join();
    }

    template<typename fn_t>
    void start(fn_t&& fn)
    {
        // Wrap the function in a lambda expression
        // of our own which will keep track of the 
        // state of the thread (or at least of the
        // function call on that thread)
        auto wrapped_fn = [&]
        {
            m_state = state_v::running;
            fn();
            m_state = state_v::stopped;
        };

        m_thread = std::make_unique<std::thread>(wrapped_fn);
    }

    bool is_running() const noexcept
    {
        return (m_state == state_v::running);
    }

private:
    enum class state_v
    {
        initial,
        running,
        stopped
    };

    std::atomic<state_v> m_state{ state_v::initial };
    std::unique_ptr<std::thread> m_thread;
    
};

void show_status(const my_thread_t& thread)
{
    if (thread.is_running())
    {
        std::cout << "thread is running\n";
    }
    else
    {
        std::cout << "thread is not running\n";
    }
}

int main()
{
    my_thread_t thread;

    show_status(thread);

    thread.start([] { std::this_thread::sleep_for(1s); });

    for (std::size_t n{0ul}; n < 6; ++n)
    {
        show_status(thread);
        std::this_thread::sleep_for(250ms);
    }
    
    return 0;
}
Pepijn Kramer
  • 9,356
  • 2
  • 8
  • 19
0

I found this little hack, and I'm spreading it like a four-year-old spreads peanut butter.

If you can use the Boost C++ libraries, you can exploit the default behavior of the boost::signals2::signal class. For returning values from called slots, these signals return the value of the last slot called, and they call in the order of connection. Also, boost::signals2::signal objects are thread safe and callable across threads. Using a boost::signals2::scoped_connection, you can connect a slot that tells you whether a thread is still running.

#include <thread>
#include <boost/signals2.hpp>

int main(int argc, char* argv[])
{
   //This can be in a class or someplace else
   boost::signals2::signal<bool ()> is_running;

   // Be sure to capture 'this' if used in a class
   is_running.connect([]() {return false;});
   
   auto t = std::thread([]()
   {
      // 'c' is an arbitrary variable name.
      boost::signals2::scoped_connection c(is_running.connect[]() {return true;}));
      // Do your stuff.
   });

   if (is_running())
      //Do stuff if the thread is still running

   t.join();

   return 0;
}

If the thread hasn't joined, but you want to see if it's completed running, you can insert an additional scope to your thread.

auto t = std::thread([]()
{
   {
      boost::signals2::scoped_connection c(is_running.connect[]() {return true;}));
      // Do your stuff.
   }
});

With this method, calling is_running() will return false even through the thread isn't joined. You'd have to analyze your code to see if you could make the is_running signal a member of your class. Alternately, you can put the actual signal object anywhere you need, and wrap the call to it in the is_running() function.

Edit: While this method is threadsafe, there is the possibility that the thread will complete between when you call your signal and completing whatever you use this logic for.

  • boost::signals2 has one big problem. it is NOT threadsafe. Adding/Removing slots while signales are being forwarded will not work. Also destructing a signal when it is also "firing" an forwarding is not safe. – Pepijn Kramer Jul 29 '23 at 18:13