C++ 具有细粒度锁的线程安全链表
在一个程序中,我有一个M类:C++ 具有细粒度锁的线程安全链表,c++,linux,multithreading,boost,linked-list,C++,Linux,Multithreading,Boost,Linked List,在一个程序中,我有一个M类: class M{ /* very big immutable fields */ int status; }; 我需要一个M型物体的链表 有三种类型的线程正在访问列表: 生产者:产生对象并将其附加到列表的末尾。所有新生成的对象都具有状态=新建。(操作时间=O(1)) 使用者:使用列表开头的对象。如果某个对象的状态为consumer\u ID,则该对象可以被消费者消费。每个消费者都保留链接列表中其可以消费的第一个项目,因此该消费
class M{
/*
very big immutable fields
*/
int status;
};
我需要一个M型物体的链表
有三种类型的线程正在访问列表:
- 生产者:产生对象并将其附加到列表的末尾。所有新生成的对象都具有状态=新建。(操作时间=O(1))
- 使用者:使用列表开头的对象。如果某个对象的状态为consumer\u ID,则该对象可以被消费者消费。每个消费者都保留链接列表中其可以消费的第一个项目,因此该消费为(摊销?)O(1)(参见下面的注释)
- 析构函数:当有通知说对象已正确使用时(操作时间=O(1))删除已使用的对象
- 修改器:根据状态图更改对象的状态。任何对象的最终状态都是使用者的id(每个对象的操作时间=O(1))
- 从生产者异步
- 从使用者异步发送
- 使用自己的计时器
您有一个常见的错误观念,认为阻塞是不好的。事实上,争用是不好的,因为它会将内核速度降低到总线速度。阻塞结束争用。阻塞是好的,因为它会取消争用线程的调度,允许非争用线程(可以全速并发运行)正如@David Schwartz所指出的,阻塞并不总是很慢,而且(在用户空间多线程应用程序中)旋转可能非常危险 此外,linux pthread库具有pthread_互斥体的“智能”实现。它被设计为“轻量级”,即当一个线程试图锁定已经获得的互斥体时,它会旋转一段时间,在阻塞之前多次尝试获取锁。尝试次数不足以损害您的系统,甚至不符合实时要求(如果有的话)。另外一个linux特有的特性是所谓的(FUTEX),它减少了系统调用的数量。其主要思想是,只有当线程确实需要阻塞互斥时,它才会执行互斥锁系统调用(当线程锁定未获取的互斥锁时,它不会执行系统调用) 事实上,在大多数情况下,你不需要重新发明轮子或引入一些非常具体的锁定技术。如果必须的话,那么要么是设计出了问题,要么是处理的是高度并发的环境(乍一看,10个消费者似乎不是这样,而且所有这些看起来都像是过度工程)
- 如果我是你,我更喜欢使用条件变量+互斥来保护列表
- 我要做的另一件事是再次检查设计。当消费者需要搜索列表是否包含ID为的项目时,为什么要使用一个全局列表(如果是,请将其删除/出列)?可能最好为每个消费者创建一个单独的列表?在这种情况下,您可能可以删除状态字段
- 读访问比写访问更频繁吗?如果是这样,最好使用R/W锁或
- 如果我对pthread原语和futex之类的东西不满意(如果我不满意,我会通过测试证明锁定原语是瓶颈,而不是消费者的数量或我选择的算法),然后我会尝试考虑复杂的算法,包括引用计数、单独的GC线程和所有更新的原子限制
boost::asio::io_service io_service_;
void produce()
{
M* m = new M;
io_service_.post(boost::bind(&consume, m));
}
void consume(M* m)
{
delete m;
}
producer()
{
while (true)
{
o = get_object_from_somewhere ()
atomic_enqueue (SQ.queue, o)
signal(SQ.sem)
}
}
consumer()
{
while (true)
{
wait (CQ[self].sem)
o = atomic_dequeue (CQ[self].queue)
process (o)
destroy (o)
}
}
modifier()
{
while (true)
{
wait (SQ.sem)
o = atomic_dequeue (SQ.queue)
FSM (o)
atomic_enqueue (CQ [o.status].queue, o)
signal (CQ [o.status].sem)
}
}