mercredi 1 février 2017

thread safety in a signal-slot system (C++11)

I have some problems designing a Signal/Slot system in C++11.

My main design goals are: simple but still offering some features and thread safe. My personal opinion on a Signal/Slot system is that emitting should be as fast as possible. Because of that I try to keep the slot list inside the signal tidy. Many other Signal/Slot systems leave disconnected slots empty. That means more slots to iterate and checking slot validity during signal emission.

Here is the concrete problem:

Signal class have one function for emitting and one function for disconnecting a slot:

template<typename... Args>
void Signal<void(Args...)>::operator()(Args&&... args)
{
    std::lock_guard<std::mutex> mutex_lock(_mutex);

    for (auto const& slot : _slots) {
        if (slot.connection_data->enabled) {
            slot.callback(std::forward<Args>(args)...);
        }
    }
}

template<typename... Args>
void Signal<void(Args...)>::destroy_connection(std::shared_ptr<Connection::Data> connection_data)
{
    std::lock_guard<std::mutex> mutex_lock(_mutex);

    connection_data->reset();

    for (auto it = _slots.begin(); it != _slots.end(); ++it) {
        if (it->connection_data == connection_data) {
            *it = _slots.back(); _slots.pop_back();
            break;
        }
    }
}

This works fine until one tries to make a connection that disconnects itself when signal i emitted:

Connection con;
Signal<void()> sig;

con = sig.connect([&]() { con.disconnect(); });
sig();

I have two problems here:

  1. The emit function must probably be redesigned because slots can potentially be removed when iterated.
  2. There are two mutex locks inside the same thread.

Is it possible to make this work (maybe with recursive mutex?), or should I redesign the system to not interfere with slots list and just leave empty slots (as many other similar projects do) when disconnecting the signal?

Aucun commentaire:

Enregistrer un commentaire