Reputation: 384
I'm following the idea here https://stackoverflow.com/a/13893381/1324674 attempting to create a thread class that will have a static pointer to a boolean flag that will tell a thread to quit (or rather, throw an exception and end). I'm having several issues however while following that same code and I'm wondering how to fix it, I've been working on this for several hours now-
using namespace std;
class InterruptThreadException {};
class InterruptibleThread {
private:
static thread_local atomic_bool* flagRef;
atomic_bool flag{false};
thread thrd;
public:
friend void checkForInterrupt( );
template < typename Function, typename... Args >
InterruptibleThread( Function&& _fxn, Args&&... _args )
: thrd(
[]( atomic_bool& f, Function&& fxn, Args&&... args ) {
flagRef = &f;
fxn( forward< Args >( args )... );
},
flag,
forward< Function >( _fxn ),
forward< Args >( _args )...
) {
thrd.detach( );
}
bool stopping( ) const {
return flag.load( );
}
void stop( ) {
flag.store( true );
}
};
thread_local atomic_bool* InterruptibleThread::flagRef = nullptr;
void checkForInterrupt( ) {
if ( !InterruptibleThread::flagRef ) {
return;
}
if ( !InterruptibleThread::flagRef->load( ) ) {
return;
}
throw InterruptThreadException( );
}
void doWork( ) {
int i = 0;
while ( true ) {
cout << "Checking for interrupt: " << i++ << endl;
checkForInterrupt( );
this_thread::sleep_for( chrono::seconds( 1 ) );
}
}
int main( ) {
InterruptibleThread t( doWork );
cout << "Press enter to stop\n";
getchar( );
t.stop( );
cout << "Press enter to exit" << endl;
getchar( );
return 0;
}
Now I believe the issue is coming from the lambda function inside of the thread call as if I remove that and just have the fxn and args call the program runs fine (obviously without the ability to terminate a thread).
Any idea what I'm doing wrong?
Errors below- visual studio:
Error C2672 'std::invoke': no matching overloaded function found
Error C2893 Failed to specialize function template 'unknown-type std::invoke(_Callable &&,_Types &&...) noexcept(<expr>)'
gnu: https://gist.github.com/anonymous/2d5ccfa9ab04320902f51d005a96d2ae
Upvotes: 1
Views: 563
Reputation: 30569
Your issue is that std::thread
stores a copy of its arguments, so the type of Function
and the type of the object passed to your lambda aren't the same.
When you call InterruptibleThread(doWork)
, Function
gets deduced to be void (&)()
: that is, reference to a function taking no arguments and returning void. When you pass fxn
along to std::thread
's constructor, it decays to void (*)()
(pointer to a function taking no arguments and returning void) and gets stored in some internal storage. When the new thread starts up that internal object gets passed as the second parameter to your lambda, but the type void (*)()
doesn't match the expected void (&)()
and everything falls apart. Similarly your constructor currently doesn't work if I pass it an lvalue reference to some functor.
A similar problem exists for args
. For example, if you pass an lvalue std::string
as one of args
, it will get passed to the lambda as a std::string&&
and things will explode.
The easiest way to fix this is to make the lambda parameter type auto
(assuming you're using c++14):
InterruptibleThread( Function&& fxn, Args&&... args )
: thrd(
[]( atomic_bool& f, auto&& fxn, auto&&... args ) {
flagRef = &f;
fxn( std::move(args)... );
},
std::ref(flag),
std::forward<Function>(fxn),
std::forward<Args>(args)... ) {
}
If you're still on c++11, you'll have to use std::decay
to get the correct parameter type:
InterruptibleThread( Function&& fxn, Args&&... args )
: thrd(
[]( atomic_bool& f,
typename std::decay<Function>::type&& fxn,
typename std::decay<Args>::type&&... args ) {
flagRef = &f;
fxn( std::move(args)... );
},
std::ref(flag),
std::forward<Function>(fxn),
std::forward<Args>(args)... ) {
}
Note in both cases I've wrapped flag
in a std::reference_wrapper
since std::atomic
is not copyable and it would be incorrect to make a copy even if it were.
Upvotes: 4