boost :: lockfree :: queue(在多线程程序中)是否可以锁定?

时间:2019-03-15 05:41:42

标签: c++ boost queue lock-free lockless

我正在开发一个程序,其中有2个以上(gstreamer)的 boost :: 线程和相同数量的虚拟应用程序的 boost :: 线程同时使用< strong>队列。现在,此队列用于 gstreamer线程与其相应的 dummy应用程序线程的任务之间的同步

该队列是一个EVENT队列:其中EVENT是一个结构

typedef struct EVENT{
    EVENT_TYPE Ev_Type;  // EVENT_TYPE is enum of Events
    EVENT_DATA Ev_Data;  // EVENT_DATA is union of data to be stored for that event
}Event_;

在谷歌搜索时,我遇到了以下两个队列选项: lockfree :: queue lockfree :: spsc_queue ,建议将lockfree::queues用于多线程应用程序。

  

混淆:为什么命名为lockFREE?是否暗示不能(互斥)锁定?

     

也请参见this example,它显示“ boost :: lockfree :: queue不是无锁的”

     

Mind = blown ...

好吧,然后我尝试按照示例(above link)并实现此队列

class Foo {
protected:
    boost::lockfree::queue<EVENT> mqSttEventQueue;
public:
    unsigned int SetEventIntoQueue(EVENT *psEvent);
};

及其定义为:

unsigned int Foo::SetEventIntoQueue(EVENT *psEvent) {
    if(mqSttEventQueue.push(*psEvent)){
         //notify that event is in queue;
    }
}

这已成功编译。但是我在这里完全处于黑暗中。

问题:

  • 为什么该示例将队列声明为

    boost::lockfree::queue<int> queue(128);

128 有什么用?是说队列大小为128(字节/项)吗? queue<int>是否在声明队列中的数据类型?

  • 为什么它对我的程序不起作用

    boost::lockfree::queue<EVENT> mqSttEventQueue(128);

如果我这样声明它,则编译错误为

error: expected identifier before numeric constant

boost::lockfree::queue<EVENT> mqSttEventQueue(128);
                                              ^~~

PS:-我真的不确定在这里放什么标题...如果可以的话,请编辑它。

1 个答案:

答案 0 :(得分:2)

  

为什么名称lockFREE?是否暗示不能(互斥)锁定?

当然可以被锁定;您将互斥锁放在数据结构之外,并使每个与数据结构接触的线程都使用它。

boost::lockfree::queue提供了unsynchronized_popunsynchronized_push,用于确保只有一个线程可以访问队列的情况。

但是lockfree::queue和无锁算法/数据结构的主要目的是不需要被锁定:多个线程可以安全地写入和/或读取同时。


“无锁”在编程中有2个含义,可能导致令人困惑但真实的陈述,例如“此无锁算法并非无锁”。

  • 临时用法:无锁的代名词-使用原子加载,存储和RMW操作(例如CAS或std::atomic::atomic_fetch_add),无需互斥体即可实现。参见例如An Introduction to Lock-Free Programming(Jeff Preshing)。也许What every systems programmer should know about concurrency

    std::shared_ptr使用无锁原子操作来管理其控制块。 C ++ 11 std::atomic<>为自定义算法提供了无锁原语。参见。通常在C ++ 11中,多个线程对同一变量的非同步访问是未定义的行为。 (除非它们都是只读的。)但是std::atomic通过选择顺序一致,获取/释放或宽松的内存排序,为您提供了明确定义的行为。

  • 计算机科学技术的含义:永远休眠或被杀死的线程不会阻塞其余线程。即保证整个程序的前进进度(至少一个线程)。 (免等待时间是线程不必重试)。参见https://en.wikipedia.org/wiki/Non-blocking_algorithm。 CAS重试循环是无锁但不是无锁的经典示例。免等待是诸如RCU(读取-复制-更新)读取线程之类的东西,或者根据定义,是将其实现为原语的硬件上的atomic_fetch_add(例如x86 xadd),而不是LL / SC或CAS重试循环。

大多数无锁的多读取器/多写入器队列在技术上都不是无锁的。通常,它们使用循环缓冲区,并且写入器将以某种方式“声明”一个条目(修复其条目)订单中的订单)。但是直到作者完成 写入条目本身后才能读取。

有关分析其可能的阻塞行为的示例,请参见Lock-free Progress Guarantees。编写器自动增加写索引,然后将数据写入数组条目。如果作者在做这些事情之间睡着了,其他作者可以填写以后的条目,而读者则只能等待索要已声明但未写的条目。 (我没看过boost::lockfree::queue<T>,但大概是 1 。)

在实践中表现出色,作者和读者之间的争用非常低。但是从理论上讲,作家可能会在错误的时刻阻塞并拖延整个队列。


脚注1:队列的另一个可行选择是链接列表。在这种情况下,您可以完全构造一个新节点,然后尝试将其添加到列表中。因此,如果您成功添加了它,则其他线程可以立即读取它,因为您已正确设置了它的指针。

但是,回收问题(安全释放其他线程可能正在读取的内存,以查看其他读者是否已经声明了内存)在垃圾回收的语言/环境之外非常棘手。 (例如Java)


  

boost::lockfree::queue<int> queue(128);为什么是128?

这是条目中的队列(最大)大小。在这种情况下的int中,因为您使用了queue<int>,所以。如上所述,大多数无锁队列使用固定大小的循环缓冲区。需要增长时,它无法像std :: vector那样重新分配和复制,因为其他线程可以同时读取它。

根据in the manualboost::lockfree::queue的第一个Google匹配)记录,explicit queue(size_type)构造函数的大小为

您还可以通过将容量用作模板参数来将容量烘焙到类型中。 (因此,容量在使用队列的所有地方都变成了编译时常量,而不仅仅是在可以通过构造函数调用进行常量传播的位置。)

该类显然不强制执行/不需要2的幂,因此模板大小参数可以通过让% capacity操作编译为带掩码而不是除法的AND来显着改善。