原子操作的成本是什么(比较和交换或原子添加/减少中的任何一个)?它消耗了多少周期?它会暂停SMP或NUMA上的其他处理器,还是会阻止内存访问?它会在无序CPU中刷新重新排序缓冲区吗?
缓存有什么影响?
我对现代流行的CPU感兴趣:x86,x86_64,PowerPC,SPARC,Itanium.
我一直在谷歌搜索C++中的无锁队列.我发现了一些代码和一些试验 - 但我没有能够编译.也欢迎无锁哈希.
摘要:到目前为止,我没有正面答案.没有"生产就绪"库,令人惊讶的是现有的库都没有符合STL容器的API.
我正在设计一个连接到一个或多个数据源流的系统,并对数据进行一些分析,而不是基于结果触发事件.在典型的多线程生产者/消费者设置中,我将有多个生产者线程将数据放入队列,并且多个消费者线程读取数据,并且消费者仅对最新数据点加上n个点感兴趣.如果慢速消费者无法跟上,生产者线程将不得不阻止,当然,当没有未经处理的更新时,消费者线程将被阻止.使用具有读取器/写入器锁的典型并发队列将很好地工作,但是进入的数据速率可能很大,因此我希望减少锁定开销,尤其是生产者的写入锁.我认为我需要一个循环无锁缓冲区.
现在有两个问题:
圆形无锁缓冲是答案吗?
如果是这样,在我自己推出之前,您是否知道任何符合我需求的公共实施?
任何指向实现循环无锁缓冲区的指针总是受欢迎的.
顺便说一句,在Linux上用C++做这件事.
一些额外的信息:
响应时间对我的系统至关重要.理想情况下,消费者线程会希望尽快看到任何更新,因为额外的1毫秒延迟可能会使系统失去价值,或者价值更低.
我倾向于的设计思想是一个半无锁的循环缓冲区,生产者线程尽可能快地将数据放入缓冲区,让我们调用缓冲区A的头部,除非缓冲区已满,否则A满足缓冲区Z的结束.消费者线程将各自保存两个指向循环缓冲区P和P n的指针,其中P是线程的本地缓冲区头,P n是P 之后的第n个项目.每个消费者线程将推进其P和P ñ一旦处理完当前P和缓冲器指针Z的端前进具有最慢P ñ.当P赶上A,这意味着没有更新的处理更新,消费者旋转并忙着等待A再次前进.如果消费者线程旋转太长时间,它可以进入休眠状态并等待条件变量,但我没关系消费者占用CPU周期等待更新,因为这不会增加我的延迟(我会有更多的CPU核心)比线程).想象一下,你有一个循环轨道,并且生产者正在一群消费者面前运行,关键是调整系统,使生产者通常比消费者领先一步,并且大部分操作都可以使用无锁技术完成.我理解获得正确实施的细节并不容易......好吧,非常难,这就是为什么我想在自己制作一些错误之前先从别人的错误中吸取教训.
在 C++ 中,有一种原子类型std::atomic<T>。该原子类型可能是无锁的,也可能不是,具体取决于类型 T 和当前平台。如果某个类型的无锁实现在类型 T 的平台上可用,那么大多数编译器都会提供无锁atomic<T>。在这种情况下,即使我想要非无锁atomic<T>我也无法拥有它。
C++ 标准决定只保留一个,std::atomic<T>而不是一std::atomic<T>加一std::lock_free<T>(部分针对特定类型实现)。这是否意味着“在任何情况下,当后者可用时,使用非无锁原子类型都会比使用无锁原子类型更好”?(主要是在性能方面而不是易用性方面)。
直到现在我std::queue在我的项目中使用.我测量了此队列上特定操作所需的平均时间.
在两台机器上测量时间:我的本地Ubuntu VM和远程服务器.使用时std::queue,两台机器的平均值几乎相同:约750微秒.
然后我"升级" std::queue到boost::lockfree::spsc_queue,所以我可以摆脱保护队列的互斥锁.在我的本地虚拟机上,我可以看到巨大的性能提升,平均现在是200微秒.然而,在远程机器上,平均值高达800微秒,这比以前慢了.
首先我认为这可能是因为远程机器可能不支持无锁实现:
并非所有硬件都支持同一组原子指令.如果硬件不可用,则可以使用防护装置在软件中进行仿真.然而,这具有失去无锁属性的明显缺点.
要确定是否支持这些指令,请boost::lockfree::queue调用一个方法bool is_lock_free(void) const;.但是,boost::lockfree::spsc_queue没有这样的功能,对我来说,这意味着它不依赖于硬件而且总是无锁 - 在任何机器上.
性能损失的原因是什么?
// c++11 compiler and boost library required
#include <iostream>
#include <cstdlib>
#include <chrono>
#include <async>
#include <thread>
/* Using blocking queue:
* #include <mutex>
* #include <queue>
*/
#include <boost/lockfree/spsc_queue.hpp>
boost::lockfree::spsc_queue<int, boost::lockfree::capacity<1024>> queue;
/* Using blocking queue:
* std::queue<int> queue;
* std::mutex mutex;
*/
int main()
{
auto producer = std::async(std::launch::async, …Run Code Online (Sandbox Code Playgroud) 假设我有一个大型数组,我想用多个线程处理内容.如果我将每个线程委托给特定的部分,保证不重叠,这是否消除了锁定的需要,假设线程不访问数组外的任何其他内存?
像这样的东西(伪代码):
global array[9000000];
do_something(chunk) {
for (i = chunk.start; i < chunk.end; i++)
//do something with array
}
main() {
chunk1 = {start: 0, end: 5000000};
chunk2 = {start: 5000000, end: 9000000};
start_thread(thread1, do_something(chunk1));
start_thread(thread2, do_something(chunk2));
wait_for_join(thread1);
wait_for_join(thread2);
//do something else with the altered array
}
Run Code Online (Sandbox Code Playgroud) 交换两个unique_ptrs并不保证是线程安全的.
std::unique_ptr<T> a, b;
std::swap(a, b); // not threadsafe
Run Code Online (Sandbox Code Playgroud)
由于我需要原子指针交换,因为我喜欢所有权处理unique_ptr,是否有一种简单的方法将它们组合起来?
编辑:如果这是不可能的,我愿意接受替代方案.我至少想做这样的事情:
threadshared_unique_ptr<T> global;
void f() {
threadlocal_unique_ptr<T> local(new T(...));
local.swap_content(global); // atomically for global
}
Run Code Online (Sandbox Code Playgroud)
在C++ 11中这样做的惯用方法是什么?
在我的多线程应用程序中,我看到其中存在严重的锁争用,从而阻碍了跨多个核的良好可伸缩性.我决定使用无锁编程来解决这个问题.
如何编写无锁结构?
lock-free ×10
c++ ×6
performance ×2
.net ×1
algorithm ×1
atomic ×1
atomic-swap ×1
boost ×1
c ×1
c# ×1
c++11 ×1
concurrency ×1
multicore ×1
stdatomic ×1
stl ×1
unique-ptr ×1