1

I would like to make a helper class (or subclass of std::thread) to allow stack size to be set and also thread name. If the thread is running on a platform, which does not support e.g. stack size, the number should just be ignored.

I was thinking of a "ThreadHelper", which has the extended constructor interface, and just returns a std::thread.

To be honest, I have very little experience when it comes to all the template stuff, which std::thread contains.

Right now the thread class is instantiated like: m_thread = new std::thread(&Button::Process, this); I would like something like: m_thread = new ThreadHelper.Create(&Button::Process, this, stackSize, name);

Any advices are appreciated.

Thanks Martin

Martin
  • 23
  • 3
  • About stack size: [bad luck](https://stackoverflow.com/questions/13871763/how-to-set-the-stacksize-with-c11-stdthread). You'll rely on OS-specific API for that. For the name, there are quite a number of alternatives, such the process function/functor accepting another name parameter. This can be handled with standard `std::thread` without extension class. – Aconcagua Jun 01 '21 at 07:10
  • I would avoid creating your `std::thread` dynamically unless you really have to (unlikely). `std::thread m_thread = std::thread(&Button::Process, this);` // should be fine – Galik Jun 01 '21 at 07:29
  • Just use boost thread, write and maintain such kind of low level cross platform code is painful – prehistoricpenguin Jun 01 '21 at 08:22

1 Answers1

1

Here is what you could do: You need a wrapper around your thread start function so that you can call the appropriate functions before (and possibly after) your thread function is running. It might also be a good idea to include a try-catch block and do some error processing.

template <typename ThreadStartFnc>
struct ThreadWrapper
{
   ThreadStartFnc fnc;
   const char *name;     // (1) Note: careful, just a pointer here, not a string. See below.
   size_t priority;
   size_t stack_size;

   ThreadWrapper(...) // constructor here...

   void operator()()
   {
      SetThreadName(name);         // Whatever that is on your system
      SetThreadPriority(priority); // dito
      SetStackSize(stack_size);    // not all systems allow this
      try {
         fnc();
      }
      catch(...) {
         cerr << "Exception caught"; // Do exception processing here.
      }
   }
};

And you need a simple way to instantiate an std::thread with the wrapper "inserted", like this:

template <typename Fnc>
std::thread make_thread(const Fnc& f, const char *name, size_t priority=0, size_t stack_size=0)
{
   return std::thread(ThreadWrapper<Fnc>(f, name, priority, stack_size));
}

And that is basically it. std::thread accepts a Functor object, which is what ThreadWrapper is. It calls Functor() to start the thread, which is void operator()(). This function uses the additional parameters name, priority, stack_size to set everything up and then call your function.

Enhance this with the C++11/14/17 goodies like variadic template parameters and/or lambda functions as you like.

Below is the actual wrapper function that we use (although we do not use a Functor, we use boost::bind and a static template function instead).

    template <typename Fnc>
    static inline void run_cstr(const Fnc &f, const char *name, DWORD priority)
    {
        int rc=_configthreadlocale(0);
/*
        _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
        setlocale(LC_ALL, "C");
*/
        SetThreadName(name);
        watchdog::set_thread_info(name);
        ::SetThreadPriority(::GetCurrentThread(), priority);
        _set_se_translator(&_se_translator);
        _set_invalid_parameter_handler(&my_invalid_parameter_handler);
        __try {
            f();
        }
        __except (global_seh_handler(GetExceptionCode(), GetExceptionInformation()) ) {
        }
    }

It sets the ThreadName for Visual Studio (SetThreadName), sets the locale, sets the thread priority, connects our software watchdog to the thread and also sets up a global Windows try/catch handler for all exceptions including access violations etc.... The global_seh_handler will be executed if any uncaught exceptions (including invalid std lib parameters) end up here. It will write a crash dump file which we use for post mortem debugging.

(1) Note: I've used a const char *name for the thread name, because I am assuming that the thread will run immediately, while the string for the thread name is still available. That is actually unsafe, if you are storing objects of type ThreadWrapper for a longer period. You'd need to change const char* to std::string then.

Hajo Kirchhoff
  • 1,969
  • 8
  • 17
  • P.S. google THREADNAME_INFO for how to set the thread name in Windows/Visual Studio – Hajo Kirchhoff Jun 01 '21 at 10:14
  • It is a great help for me. I am not able to compile a reduced example: `template struct ThreadWrapper { ThreadStartFnc fnc; ThreadWrapper(ThreadStartFnc _fnc) { fnc = _fnc; } void operator()() { fnc(); } }; template std::thread make_thread(const Fnc& f) { return std::thread(ThreadWrapper(f)); } void TestThread() {} int main() { std::thread t = make_thread(TestThread); } ` I get an error: 'ThreadWrapper::fnc': a member of a class template cannot acquire a function type – Martin Jun 03 '21 at 09:04
  • That's because void TestThread(){} is not a Functor. It has no operator(), implicitly or explicitly. But don't change TestThread just now. My guess is ``` std::thread t = make_thread(&TestThread); ``` will work, since Fnc will be of void(*)() then. IOW, a pointer to a static function. And that pointer _can_ be called with fnc(); A Functor is anything for which the expression `fnc()` is valid. IOW, anything that can be called. Problem is, there is no type that can accept a value of "TestThread". Only "TestThread*" so to speak. So your problem is with the parameter of make_thread – Hajo Kirchhoff Jun 03 '21 at 11:05
  • Using a pointer worked. Thanks! It has been such a great help. Now I will try to implement the required functionality for setting the stack size (runs on FreeRTOS embedded OS). – Martin Jun 03 '21 at 12:15