我应该按什么顺序发送callback()并通知服务员?

In which order should I send callback() & notify waiters?

本文关键字:通知 服务员 callback 什么 顺序 我应该      更新时间:2023-10-16

我有一个类,通过它可以异步提供一些服务(也可以同步进行相同的调用)。当被请求时,这个类的对象(比如操作符)在不同的线程中启动操作。其他对象可以注册到操作符对象的通知中,以便在操作结束时对该对象调用OperationEnded()方法。其他对象也可以通过对运算符对象调用wait()来等待此操作的完成。

操作结束时的代码大致如下:

_opEndedMutex.lock();
_thereIsOngoingOp = false;
_opEndedCondition.notify_all();
_opEndedMutex.unlock();
//no more call after notification
m_spNotificationManager->OperationEnded();

wait()函数如下:

boost::unique_lock<boost::mutex> lock(_opEndedMutex);
while(_thereIsOngoingOp)
{
_opEndedCondition.wait(_opEndedMutex);
}

问题在于资源管理。这是一个C++类,因此当检测到操作结束时,该类的用户可以删除运算符对象(如果有任何活动操作,则析构函数将等待完成)。可以通过等待或接受通知来检测操作的结束。因此,如果我首先调用_opEndedCondition.notify_all(),并且用户删除了运算符对象,那么它可能会在尝试调用OperationEnded()时崩溃,因为m_spNotificationManager已被删除。如果选择首先调用OperationEnded(),并且用户在此调用过程中删除了运算符对象,那么它可能会在尝试访问_opEndedMutex、_thereIsOngoingOp和_opEnded条件时崩溃。

作为一个解决方案,我首先想到的是用一个新的互斥对象来保护这两个调用。这似乎不太好,因为我无法预见如果引入一个新的互斥体,并且如果在OperationEnded()通知中用户同步启动一个新操作,会发生什么。我也不知道如何在wait()方法中使用新的互斥对象。

注1:这个API既用于我们公司的应用程序,也用于其他公司的应用。因此,我无法摆脱任何一种同步机制。

注2:我修改了原始代码、变量和方法的名称,所以可能会有拼写错误,但想法应该很清楚。

第1版:

操作员对象保留在通过工厂生成的共享库中,然后通过接口暴露给外部世界。因此操作员对象的典型寿命为:

IOperator * op = factory:getNewOperator();
//perform operations with op
op->Release() //this one goes and deletes the op object

还要注意,操作符对象是可重用的。客户端代码可以使一个新的操作符对象多次使用它,并在最后将其删除。

您有几个解决方案:

  • 您可以从std::enable_shared_from_this专门化您的操作对象。这意味着在客户端代码中,您不再删除对象,而是将std::shared_ptr设置为nullptr,并且不关心对象何时被实际删除。

  • 您可以在定时器延迟的情况下实现有限的垃圾收集:当您在客户端代码中收到OperationEnded()通知时,您将指针放在队列中,并附上添加对象的时间戳。然后,队列将有一个活动对象,该对象在计时器上唤醒,占用当前时间,如果时间戳(比方说)比当前时间早至少5秒,则将其删除。

  • 您可以使用对象池来分配和取消分配操作类。当一个对象不再使用时,它实际上不会被删除,而是作为一个可用(回收)对象放在池中。当池被销毁时,在您完成处理后,该对象实际上会被删除。

  • 您可以为您的对象创建一个生存期管理器,该管理器将删除您的操作对象,然后发送通知。

  • 您可以在最后让您的"OperationEnded"函数调用delete this;;然后,您将实现客户端代码,以便在接收到通知时简单地将指针设置为NULL;然而,这样的解决方案很脆弱,而且在您的实现中,这可能只是将问题转移到另一段代码上。

  • 最后,您可以实现所有这些的自定义组合。

我们通过计算异步操作解决了这个问题。

当构造一个新的运算符对象时,我们将其指针添加到引用计数表。当这个对象启动异步操作时,我们增加引用计数,当操作结束,我们递减引用计数。

当对一个对象调用release时,如果引用计数为0,我们将删除它。如果它大于0我们将指针保持在soonToBeDeletables向量中。因此,当操作结束时,如果引用计数为0,并且如果执行操作的运算符对象在sooToBeDeletables向量中,我们删除运算符对象。

当异步操作结束时,我们首先向条件变量发出信号,然后调用OperationEnded()。但可能反过来也会奏效。

通过这种方法,现在可以解决数据竞争,并且删除是确定的。我们不需要一个后台工作线程,它会在一段时间后唤醒并进行删除。确定性是必要的,因为当几个对象因此被构造、使用(同步)和删除时,那么在一段时间内,几个对象将同时驻留在存储器中,这在存储器受限(移动)系统中是不希望的。

经验教训:尽可能只提供一个通知机制。