强制线程在销毁前离开对象

4

在使用多线程时,我经常遇到以下问题:

我有一个对象,比如一个网络接收器(但也可能是其他任何东西),还有一个获取数据的函数。现在有时候没有数据可用,你希望让线程等待获取它的数据。这是一个阻塞调用,非常类似于伯克利套接字及其衍生实现所使用的方式。

原理很简单:

Forgive my mediocre illustrational skills. I'm a programmer after all:)

当然,还有其他实现方式。但我的通常实现方式是使用C++11,具体如下:

  • Object A 在一个专门负责此任务的单独线程中调用Object B中的函数。
  • Object B 使用std::condition_variable构造来阻塞线程,直到实际获取数据。
  • Object A 将数据放入队列中,由主线程读取。

现在,如果必须在object A之前销毁object B(在阻塞调用上返回nullptr或类似内容),那么我实际上遇到的问题就出现了。我真的不知道如何有效地包装object B

主要问题是object B不知道线程的存在,而线程只能有一个句柄,这个句柄在object A中。

示例:

下面是一些代码来说明我的问题。

假设我在Object B中有以下函数:

data* getData
{
    std::unique_lock<std::mutex>l_newDataWaiterLock(m_newDataWaiterMutex);
    m_newDataWaiter.wait(l_newDataMutex);
    if(!running)
       return nullptr
    else 
       return data;
}

还有这个析构函数:

~ObjectB()
{
    m_running = false;
    m_newDataWaiter.notifyAll();
    //Point X
}

有以下这些成员变量:

std::condition_variable m_newDataWaiter;
std::atomic<bool> m_running;

我们仍然存在一个问题,即析构函数将不得不在指定的"X点"等待,直到所有其他线程收到通知并返回空值。
现在,我可以使用原子计数器、更多的std::condition_variable和互斥锁来解决这个问题。但是,我有一种感觉,必须有一种更优雅和可靠的解决方案 : )。由于这个解决方案需要在整个B对象的生命周期中的每个getData调用中发出通知。
注意:我正在使用C++11,所以我已经用它来说明一切。我希望使用它来解决这个问题。虽然这当然是一个更普遍的并发问题。
2个回答

2
如何使用 std::shared_ptr 对对象 B 进行管理,并使用 std::weak_ptr 将指针存储在 A 中呢?
这种方法略微低效,因为每次当 A 想要访问时,它需要通过 std::weak_ptr::lock() 临时获取一个 std::shared_ptr,但您可以确信不会再出现竞态条件了。

啊,是的,那确实是一个解决方案。但是那样做也会阻止析构函数被调用,对吗?因此需要先调用Object B来指示它即将被销毁。或者我理解错了吗? - laurisvr
1
@laurisvr 这就是 std::weak_ptr 的美妙之处:它不会阻止所引用的对象被销毁。只有在您(暂时)拥有一个 std::shared_ptr 来放置数据时,才会防止其被销毁。只需使用 if( const auto sp = wp.lock() ) { sp->insert(data); } 检查结果即可。整个生命周期管理很难在没有看到整个代码的情况下进行判断,但可以试试! - Daniel Frey

1

如果必须在对象A之前销毁对象B,则我的实际问题就会出现。

在这里,我描述了另一种处理可能在其中运行线程的对象实例删除的方法。嵌入式系统的行为可能与您的设计有很大不同...但也许它可以激发您以新的方式看待挑战。


摘要:使用废纸篓和清洁工。

当需要“移除”一个可能有其他线程在其中工作的对象时

 1) copy pointer-to-object   to the 'waste-basket' fifo
    A* a;   waste_basket.push_back(a);  

 2) copy pointer-to-replacement-object to replace pointer-to-object
    A* t = new(replacementObject);
    a = t;  

 Use low priority janitor thread to inspect waste-basket periodically
 and delete any resident older than max duration. Max duratin ensures 
 any other thread activity has completed.

在垃圾桶中的n秒,防止对象在线程仍在使用时被删除。(您的持续时间可能有所不同。)
在我所工作的嵌入式系统中,有28个被动(没有内部线程)对象实例在多态表格(货架上的卡片)中,并且大约有10多个线程可能随时通过这些卡片实例之一与硬件进行交互。即使操作员(在UI线程上)命令删除第j个实例(货架上的第j张卡片),该对象也无法被删除,直到所有当前执行其中的线程完成其当前活动为止。由于每个方法的短暂性质(<25ms),我们没有使用计数或信号量来确定实例的未访问状态。相反,我们将删除转换分为两个步骤,并推迟了卡片的实际删除。第1步:将表格条目shelf[j]复制到“废纸篓”FIFO列表中;第2步:将表格条目shelf[j]替换为“空槽”实例指针,所有使用货架的线程都知道如何对其进行多态使用(就像它只是另一张卡片)。这两个步骤使逻辑上的删除对用户和线程来说似乎是瞬间的。但是,实际删除会在什么时候发生?

当现有的线程完成其活动时,它们必须离开实例。但这需要多长时间呢?

我们得出的要求是,在对象实例中“工作”的任何线程应在小于25毫秒的时间内完成该活动(基于设计的其他方面)。从实际角度来看,大多数线程完成得更快,没有线程超过这个时间。

因此,为了方便嵌入式系统的使用,团队决定废纸篓的“保持”时间应大于等于1秒。

请注意,当移除的卡片位于废纸篓中时,没有其他线程可以访问它或在该对象中启动另一个活动。当在架子上发现一个“空槽”卡片时,其他线程会被误导。如果还没有从内存中删除,则逻辑上该卡片已经消失了。

为了完成这个设计,我们添加了一个低优先级的清洁工任务来定期处理废纸篓列表。清洁工检查实例进入废纸篓的到达时间,并且直到排队时间至少为1秒后才删除任何卡片。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接