2

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?

sehe
  • 374,641
  • 47
  • 450
  • 633
Manuel Barbe
  • 2,104
  • 16
  • 21

1 Answers1

6

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

  1. lock destruction of the class until it's "idle"
  2. bind the signal handler to the instance using 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()

Full Listing

#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

sehe
  • 374,641
  • 47
  • 450
  • 633
  • But still, how do you explain the fact that the slot was invoked? He disconnected the slot before the signal fired (this is what I see by checking the "sleep" statements). – Alex Shtoff Jun 23 '14 at 12:56
  • @Alex the signal fires immediately, way before the disconnect. It's just that the handler takes a while to complete. – sehe Jun 23 '14 at 13:01
  • 1
    I really like the shared_ptr approach. Thanks for your detailed answer. – Manuel Barbe Jun 23 '14 at 13:12
  • Can you elaborate on? "lock destruction of the class until it's "idle"" Also do we have to use `boost::shared_ptr` or is it possible to use `std::shared_ptr`? – pooya13 Feb 06 '19 at 01:21