2

我正在开发一个程序,其中 2+ (gstreamer) boost::线程和相同数量的boost::虚拟应用程序的线程同时使用queue。现在这个队列用于gstreamer 线程的任务与其对应的虚拟应用程序线程之间的同步

该队列是一个 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::queuelockfree::spsc_queue,这表明lockfree::queues它们用于多线程应用程序。

困惑:为什么叫 lockFREE?是否暗示不能(互斥)锁定?

另见这个例子,它说“boost::lockfree::queue is not lockfree”

头脑=吹...

好吧,然后我尝试按照示例(以上链接)并实现此队列

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:-我真的不确定在这里放什么标题...如果可以的话,请编辑它。

4

1 回答 1

2

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

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

boost::lockfree::queue提供unsynchronized_popunsynchronized_push在您确保只有一个线程可以访问队列的情况下使用。

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


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

  • 随意使用:无锁的同义词- 没有互斥锁,使用原子加载、存储和 RMW 操作(如 CAS 或std::atomic::atomic_fetch_add. 例如,参见无锁编程简介(Jeff Preshing)。也许每个系统程序员都应该了解并发

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

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

大多数无锁多读取器/多写入器队列在技术意义上不是无锁的。 通常它们使用循环缓冲区,并且写入者会以某种方式“声明”一个条目(固定其在队列中的顺序)。但在作者完成对条目本身的写入之前无法读取它。

有关分析其可能的阻塞行为的示例,请参阅无锁进度保证。写入器原子地增加写入索引,然后将数据写入数组条目。如果作者在做这些事情之间睡着了,其他作者可以填写后面的条目,而读者则被困在等待声称但未写入的条目。(我没看过boost::lockfree::queue<T>,但大概是类似的1。)

在实践中,作者和读者之间的竞争非常低,性能非常好。但理论上,作家可能会在错误的时刻阻塞并拖延整个队列。


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

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


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

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

手册中所述(第一次谷歌点击boost::lockfree::queue),explicit queue(size_type)构造函数需要一个大小。

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

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

于 2019-03-15T06:38:01.593 回答