【发布时间】:2011-10-28 23:01:58
【问题描述】:
在一个程序中,我有一个 M 类:
class M{
/*
very big immutable fields
*/
int status;
};
我需要一个 M 类型对象的链表。
三种类型的线程正在访问列表:
- 生产者:生产对象并将其附加到列表的末尾。所有新生成的对象的状态=NEW。 (操作时间 = O(1))
- Consumers:使用列表开头的对象。如果一个对象的 status=CONSUMER_ID,它可以被消费者消费。每个消费者都保留它可以消费的链表中的第一项,因此消费是(摊销?)O(1)(见下面的注释)。
- 析构函数:当有通知指出对象已被正确使用时删除已使用的对象(操作时间 = O(1))。
- 修改器:根据状态图更改对象的状态。任何对象的最终状态都是消费者的 id(每个对象的操作时间 = O(1))。
消费者的数量少于 10 个。生产者的数量可能多达几百个。有一个修饰符。
注意:修饰符可能会修改已经消费的对象,因此消费者存储的物品可能会来回移动。对于这个问题,我没有找到更好的解决方案(虽然,对象之间的比较是O(1),操作不再是摊销O(1))。
性能非常重要。因此,我想使用原子操作或细粒度锁(每个对象一个)来避免不必要的阻塞。
我的问题是:
首选原子操作,因为它们更轻。我想我必须使用锁来更新析构线程中的指针,并且我可以使用原子操作来处理其他线程之间的争用。请让我知道我是否遗漏了什么,并且有一个原因是我不能在状态字段上使用原子操作。
我认为我不能使用 STL 列表,因为它不支持细粒度锁。但是您会推荐使用 Boost::Intrusive 列表(而不是自己编写)吗? Here 提到侵入式数据结构更难使线程安全?细粒度锁也是这样吗?
-
生产者、消费者和析构函数将根据某些事件异步调用(我计划使用 Boost::asio。但我不知道如何运行修饰符以尽量减少与其他线程的争用。选项是:
- 与生产者异步。
- 与消费者异步。
- 使用自己的计时器。
只有在某些条件成立时,任何此类调用才会在列表上运行。我自己的直觉是,我如何称呼修饰符没有区别。我错过了什么吗?
我的系统是 Linux/GCC,我正在使用 boost 1.47 以防万一。
类似问题:Thread-safe deletion of a linked list node, using the fine-grained approach
【问题讨论】:
-
首先,为什么需要析构函数?如果消费者在出队后正确消费了一个对象,它可以销毁该对象本身。如果出现允许稍后重试的类型的错误,它可以适当地设置状态并将其推回列表中。
-
为了简单起见,我会放弃使用三个列表的想法。一种用于 Producer->Modifier,一种用于 Modifier->Consumer,一种用于 Consumer->Destructor。我认为它会简化代码,如果不是锁定的话。
-
我很想允许修改器在从列表中提取其目标对象的短暂时间内锁定整个列表。然后它可以在不受其他实体干扰的情况下进行修改,然后重新加载对象。
-
@Martin。感谢您的回答。我需要一个析构函数,因为在消费之后,对象可能会或可能不会被安排重新消费。我无法再次插入对象,因为插入对象的顺序很重要(它们最初是排序的)。
-
@Mooding 感谢您的回答。同样的原因会阻止使用多个列表。每个列表都应该保持排序,我不能在 O(1) 中的另一个列表中间插入一个对象。总之,不能重新插入任何对象。
标签: c++ linux multithreading boost linked-list