线程安全的递归迭代

时间:2018-11-27 12:56:46

标签: c++ multithreading

我正在C ++应用程序中实现观察者模式。到目前为止,我有一个定义如下的基类:

template<typename CallbackType,
         typename LockType    = utils::Lock<boost::mutex>,
         typename StorageType = std::vector<std::shared_ptr<CallbackType>>>
class CallbackManager
{
    LockType mCallbackLock { "commons::CallbackManager", __FILE__, __LINE__ };
    StorageType mCallbacks;

    virtual uint64_t registerCallback( const std::shared_ptr<CallbackType>& aCallbackPtr );
    virtual bool unregisterCallback( uint64_t aCallbackId );
    void iterateSubscribers(const std::function<void(typename StorageType::value_type&)>&);
};

其中一项关键要求是能够(递归地)在registerCallback上下文内执行unregisterCallback / iterateSubscribers调用。

iterateSubscribers函数以每个回调作为参数执行aFnc的迭代调用。 aFnc可能执行registerCallback / unregisterCallback呼叫。当前实现如下:

void iterateSubscribers(const std::function<void(typename StorageType::value_type&)>& aFnc)
{
    decltype(mCallbacks) callbacks;

    {
        // BEGIN SCOPED LOCK
        auto lock = mCallbackLock.getScopedLock();
        boost::ignore_unused( lock ); // To supress the warning in case NoLock is used

        callbacks = mCallbacks;
        // END SCOPED LOCK
    }

    for( auto it = callbacks.begin(); it != callbacks.end(); ++it )
    {
        aFnc( *it );
    }
}

我正在执行回调容器的副本,从性能的角度来看,这并不是一个很好的选择,但是可以解决两个问题:

  • mCallbacks修改不会中断循环
  • 获取新锁不会导致死锁

这种折衷对我来说是可以接受的,我不会通知新添加的订户,也不会跳过最近删除的订户的通知。

我无法接受的解决方法是:

  • 不可复制的容器作为StorageType的模板参数将导致iterateSubscribers函数的编译错误

一种可能的解决方案是将这样的容器或其value_type定义为指针,但是由于我的原因,这负担不起。

我还介绍了另一种解决方案-不要执行复制,用递归互斥锁替换常规互斥锁,并在所持有的锁下执行所有调用,但这有一个问题。一旦元素被添加或删除,循环将被破坏:

for( auto it = callbacks.begin(); it != callbacks.end(); ++it )

是否还有其他技术/想法来实现所需的行为?

编辑:

我走在重构的道路上。将旧版回调容器的value_type从回调副本更改为shared_ptr。这使我能够复制容器,而不必依赖于低估value_type的TriviallyCopyable状态。

0 个答案:

没有答案