我正在开发一个程序,其中有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:-我真的不确定在这里放什么标题...如果可以的话,请编辑它。
答案 0 :(得分:2)
为什么名称lockFREE?是否暗示不能(互斥)锁定?
当然可以被锁定;您将互斥锁放在数据结构之外,并使每个与数据结构接触的线程都使用它。
boost::lockfree::queue
提供了unsynchronized_pop
和unsynchronized_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<>
为自定义算法提供了无锁原语。参见stdatomic。通常在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 manual(boost::lockfree::queue
的第一个Google匹配)记录,explicit queue(size_type)
构造函数的大小为
您还可以通过将容量用作模板参数来将容量烘焙到类型中。 (因此,容量在使用队列的所有地方都变成了编译时常量,而不仅仅是在可以通过构造函数调用进行常量传播的位置。)
该类显然不强制执行/不需要2的幂,因此模板大小参数可以通过让% capacity
操作编译为带掩码而不是除法的AND来显着改善。