With boost signals (which is now deprecated) I always wrapped connection management and signal invocation with a mutex in order to be thread-safe. Boost signals 2 should give that out-of-the-box. But:
According to the Boost Signals2 Thread-Safety documentation, it is possible to disconnect slots from thread A while they are executing on thread B. Let's assume I created an object O on thread A and connected a member function of O to a signal S that is executed on worker thread B. Now, for some reasons O needs to be destroyed and thus disconnected from S before. Here is an example:
#include <iostream>
#include <boost/thread.hpp>
#include <boost/signals2.hpp>
#include <boost/bind.hpp>
using namespace std;
using namespace boost;
struct Object
{
Object() : x(0) {cout << __PRETTY_FUNCTION__ << endl;}
virtual ~Object() {cout << __PRETTY_FUNCTION__ << endl;}
void doSomething()
{
this_thread::sleep(posix_time::milliseconds(4200));
cout << "Accessing member in " << __PRETTY_FUNCTION__ << endl;
x++;
}
int x;
};
class Worker
{
public:
Worker() {}
virtual ~Worker() {myThread.join();}
void Start() { myThread = thread(bind(&Worker::DoThread, this)); }
signals2::signal<void ()> Signal;
private:
void DoThread()
{ // thread B
Signal();
}
thread myThread;
};
int main(int argc, char* argv[])
{
Worker w;
{ // thread A
Object o;
signals2::connection bc = w.Signal.connect(bind(&Object::doSomething, &o));
w.Start();
this_thread::sleep(posix_time::seconds(2));
bc.disconnect();
}
return 0;
}
Executing this code prints:
Object::Object()
virtual Object::~Object()
Accessing member in void Object::doSomething()
As we can see, I'm accessing an already destroyed object. So, finally I ended up wrapping the signal with a mutex again.
connection Worker::Connect(..) { mutex::scoped_lock l(_mutex); Signal.connect(..); }
void Worker::Disconnect(connection c) { mutex::scoped_lock l(_mutex); c.disconnect(); }
void Worker::Raise() { mutex::scoped_lock l(_mutex); Signal(); }
Am I missing something? Is there an easier way to safely disconnect from boost signals 2?
I think the problem is actually with your object.
The Object is not threadsafe, however, you seem to be simultaneously executing a member function (the signal handler) and it's destructor.
The solution, then, would be to remove this race condition. Either
boost::shared_ptr
/boost::shared_from_this
. That way, you don't have to explicitly manage the lifetime at all, and the destructor will just "magically" run after the signal handler (assuming that it got disconnected in the mean time), because that's when the last reference to the bind-expression[1] goes out of scope.Here's what I mean Live On Coliru, printing:
Object::Object()
Accessing member in void Object::doSomething()
virtual Object::~Object()
#include <iostream>
#include <boost/enable_shared_from_this.hpp>
#include <boost/make_shared.hpp>
#include <boost/thread.hpp>
#include <boost/signals2.hpp>
#include <boost/bind.hpp>
using namespace std;
using namespace boost;
struct Object : boost::enable_shared_from_this<Object>
{
Object() : x(0) {cout << __PRETTY_FUNCTION__ << endl;}
virtual ~Object() {cout << __PRETTY_FUNCTION__ << endl;}
void doSomething()
{
this_thread::sleep(posix_time::milliseconds(4200));
cout << "Accessing member in " << __PRETTY_FUNCTION__ << endl;
x++;
}
int x;
};
class Worker
{
public:
Worker() {}
virtual ~Worker() {myThread.join();}
void Start() { myThread = thread(bind(&Worker::DoThread, this)); }
signals2::signal<void ()> Signal;
private:
void DoThread()
{ // thread B
Signal();
}
thread myThread;
};
int main()
{
Worker w;
{ // thread A
auto o = boost::make_shared<Object>();
signals2::connection bc = w.Signal.connect(bind(&Object::doSomething, o));
w.Start();
this_thread::sleep(posix_time::seconds(2));
bc.disconnect();
}
return 0;
}
[1] Or C++11 lambda, of course