Reputation: 2474
I want to implement a small thread wrapper that provides the information if a thread is still active, or if the thread has finished its work. For this I need to pass the function and its parameters to be executed by the thread class to another function. I have a simple implementation that should work but cannot get it to compile, and I can't figure out what to do to make it work.
Here is my code:
#include <unistd.h>
#include <iomanip>
#include <iostream>
#include <thread>
#include <utility>
class ManagedThread
{
public:
template< class Function, class... Args> explicit ManagedThread( Function&& f, Args&&... args);
bool isActive() const { return mActive; }
private:
volatile bool mActive;
std::thread mThread;
};
template< class Function, class... Args>
void threadFunction( volatile bool& active_flag, Function&& f, Args&&... args)
{
active_flag = true;
f( args...);
active_flag = false;
}
template< class Function, class... Args>
ManagedThread::ManagedThread( Function&& f, Args&&... args):
mActive( false),
mThread( threadFunction< Function, Args...>, std::ref( mActive), f, args...)
{
}
static void func() { std::cout << "thread 1" << std::endl; }
int main() {
ManagedThread mt1( func);
std::cout << "thread 1 active = " << std::boolalpha << mt1.isActive() << std::endl;
::sleep( 1);
std::cout << "thread 1 active = " << std::boolalpha << mt1.isActive() << std::endl;
return 0;
}
The compiler error I get:
In file included from /usr/include/c++/5/thread:39:0,
from prog.cpp:4:
/usr/include/c++/5/functional: In instantiation of 'struct std::_Bind_simple<void (*(std::reference_wrapper<volatile bool>, void (*)()))(volatile bool&, void (&)())>':
/usr/include/c++/5/thread:137:59: required from 'std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(volatile bool&, void (&)()); _Args = {std::reference_wrapper<volatile bool>, void (&)()}]'
prog.cpp:28:82: required from 'ManagedThread::ManagedThread(Function&&, Args&& ...) [with Function = void (&)(); Args = {}]'
prog.cpp:35:28: required from here
/usr/include/c++/5/functional:1505:61: error: no type named 'type' in 'class std::result_of<void (*(std::reference_wrapper<volatile bool>, void (*)()))(volatile bool&, void (&)())>'
typedef typename result_of<_Callable(_Args...)>::type result_type;
^
/usr/include/c++/5/functional:1526:9: error: no type named 'type' in 'class std::result_of<void (*(std::reference_wrapper<volatile bool>, void (*)()))(volatile bool&, void (&)())>'
_M_invoke(_Index_tuple<_Indices...>)
^
Live example is available here: https://ideone.com/jhBF1q
Upvotes: 8
Views: 2690
Reputation: 15377
The following is simple, elegant, and to my knowledge the most-correct of all current answers (see below):
template < class Function, class... Args >
ManagedThread::ManagedThread( Function&& fn, Args&&... args ) :
mActive(false),
mThread(
[this]( auto&& fn2, auto&&... args2 ) -> void {
mActive = true;
fn2(std::forward<Args>(args2)...);
mActive = false;
},
std::forward<Function>(fn), std::forward<Args>(args)...
)
{}
The answer by DeiDei has a subtle but important flaw: the lambda takes the stack variables by reference, so if the thread starts, and tries to use, the stack variables after the constructor returns, you can get a stack-use-after-free error. Indeed, compiling with -fsanitize=address
is typically sufficient to demonstrate the problem.
The answer by O'Neill does not work when e.g. any arguments are lvalues, and is a bit clunky.
The answer by jotik is close, but does not work when the arguments are lvalues either, nor if they are member functions.
Upvotes: 2
Reputation: 3849
In the error message, you can see the difference void (*)()
vs void (&)()
. That's because std::thread's constructor parameters are std::decay
ed.
Add also std::ref
to f
:
template< class Function, class... Args>
ManagedThread::ManagedThread( Function&& f, Args&&... args):
mActive( false),
mThread( threadFunction< Function, Args...>, std::ref(mActive), std::ref(f), std::forward<Args>(args)...)
{
}
Upvotes: 8
Reputation: 17900
O'Neil and DeiDei got here first, and they're correct as far as I can tell. However, I'm still posting my solution to your problem.
Here's something that would work better:
#include <atomic>
#include <thread>
#include <utility>
class ManagedThread {
public: /* Methods: */
template <class F, class ... Args>
explicit ManagedThread(F && f, Args && ... args)
: m_thread(
[func=std::forward<F>(f), flag=&m_active](Args && ... args)
noexcept(noexcept(f(std::forward<Args>(args)...)))
{
func(std::forward<Args>(args)...);
flag->store(false, std::memory_order_release);
},
std::forward<Args>(args)...)
{}
bool isActive() const noexcept
{ return m_active.load(std::memory_order_acquire); }
private: /* Fields: */
std::atomic<bool> m_active{true};
std::thread m_thread;
};
It makes use of lambdas instead, and correctly uses std::atomic<bool>
instead of volatile
to synchronize the state, and also includes the appropriate noexcept()
specifiers.
Also note, that the underlying std::thread
is not joined or detached properly before destruction, hence leading to std::terminate()
being called.
I rewrote the test code as well:
#include <chrono>
#include <iostream>
int main() {
ManagedThread mt1(
[]() noexcept
{ std::this_thread::sleep_for(std::chrono::milliseconds(500)); });
std::cout << "thread 1 active = " << std::boolalpha << mt1.isActive()
<< std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "thread 1 active = " << std::boolalpha << mt1.isActive()
<< std::endl;
}
Upvotes: 6
Reputation: 10415
The answer by @O'Neil is correct, but I would like to offer a simple lambda approach, since you've tagged this as C++14
.
template<class Function, class... Args>
ManagedThread::ManagedThread(Function&& f, Args&&... args):
mActive(false),
mThread([&] /*()*/ { // uncomment if C++11 compatibility needed
mActive = true;
std::forward<Function>(f)(std::forward<Args>(args)...);
mActive = false;
})
{}
This would eliminate the need for an external function all together.
Upvotes: 5