关键字:阻塞队列、非阻塞队列、性能对比、Non-BlockingQueue、BlockingQueue、benchmark、Performance
原文作者:@玄冬Wong
转载请注明出处:http://aigo.iteye.com/blog/2292169
对阻塞和非阻塞队列进行测试:
1,用boost::lockfree::spsc_queue非阻塞队列;(注:spsc_queue是单个生产者单个消费者线程安全的队列,多个生产者多个消费者线程安全的boost::lockfree::queue理论上没有spsc_queue性能好,当然实际上也是这样。。)
2,使用boost::mutex、boost::condition_variable实现的阻塞队列;
先公布结果:
1,如果队列容量足够大,那么非阻塞队列完爆阻塞队列,速度领先至少一个数量级;
2,如果队列容量相对数据总量较小,那么阻塞队列完爆非阻塞队列,速度领先也是一个数量级以上;
现在的服务器动辄32G内存,至于哪种队列更适合服务器,你懂的:)
测试代码:
#include <windows.h> #include <iostream> #include <time.h> #include <thread> #include <list> #include <boost/lockfree/spsc_queue.hpp> #include <boost/thread/mutex.hpp> #include <boost/thread/condition_variable.hpp> #include <boost/circular_buffer.hpp> #define LOOP_COUNT 10000000 #define QUEUE_CAPACITY 10000000 boost::condition_variable cv; boost::mutex m; boost::circular_buffer<int> cb(QUEUE_CAPACITY); boost::lockfree::spsc_queue<int, boost::lockfree::capacity<QUEUE_CAPACITY>> spscq; int count = 0; void blocking_productor() { for (int i = 0; i < LOOP_COUNT; i++) { boost::unique_lock<boost::mutex> lock(m); while (cb.size() == QUEUE_CAPACITY) { cv.wait(lock); } cb.push_back(i); cv.notify_one(); } } void blocking_customer() { while (1) { boost::unique_lock<boost::mutex> lock(m); while (cb.size() == 0) { cv.wait(lock); } cb.pop_front(); cv.notify_one(); if (++count >= LOOP_COUNT) { break; } } } void nonblocking_productor() { for (int i = 0; i < LOOP_COUNT; i++) { while (spscq.write_available() == 0) { Sleep(1); } spscq.push(i); } } void nonblocking_customer() { while (1) { if (spscq.pop()) { ++count; } else { Sleep(1); } if (count >= LOOP_COUNT) { break; } } } void test_blocking() { clock_t start = clock(); std::thread *bc_t = new std::thread((&blocking_customer)); std::thread *bp_t = new std::thread((&blocking_productor)); bc_t->join(); bp_t->join(); clock_t end = clock(); printf("[test_blocking]\nQUEUE_CAPACITY:%d\ncost:%dms\n", QUEUE_CAPACITY, end - start); printf("count:%d\n", count); delete bc_t; delete bp_t; } void test_nonblocking() { clock_t start = clock(); std::thread *nc_t = new std::thread((&nonblocking_customer)); std::thread *np_t = new std::thread((&nonblocking_productor)); nc_t->join(); np_t->join(); clock_t end = clock(); printf("[test_nonblocking]\nQUEUE_CAPACITY:%d\ncost:%dms\n", QUEUE_CAPACITY, end - start); printf("count:%d\n", count); delete nc_t; delete np_t; } int main(char* args, int size) { //为了排除测试程序的无关因素,测试时只开启一个:blocking或者nonblocking。 //test_blocking(); test_nonblocking(); }
不同队列容量的测试结果:
读写线程分别执行一千万次的写入和读取操作
阻塞
[test_blocking]
QUEUE_CAPACITY:10
cost:13758ms
count:10000000
[test_blocking]
QUEUE_CAPACITY:1000
cost:1320ms
count:10000000
[test_blocking]
QUEUE_CAPACITY:10000000
cost:1178ms
count:10000000
非阻塞
[test_nonblocking]
QUEUE_CAPACITY:1000
cost:20330ms
count:10000000
[test_nonblocking]
QUEUE_CAPACITY:10000
cost:1827ms
count:10000000
[test_nonblocking]
QUEUE_CAPACITY:100000
cost:195ms
count:10000000
[test_nonblocking]
QUEUE_CAPACITY:1000000
cost:92ms
count:10000000
[test_nonblocking]
QUEUE_CAPACITY:10000000
cost:90ms
count:10000000
总结:
读写线程分别执行一千万次的写入和读取操作,
阻塞模式下,1000的队列容量就可以将性能发挥到极限:耗时1200毫秒左右;
非阻塞模式下,一百万的队列容量才能将性能发挥到极限:耗时90毫秒以内;
测试环境:
boost 1.60
windows 10 pro x64
VS2015企业版 update2,release x64
CPU:i7二代移动版
补充内容:
1,阻塞和非阻塞的适用环境
这篇文章讲得比较好:如果队列空闲状态占多数,那么阻塞队列性能更好,如果队列有待读取元素的情况占多数,那么非阻塞的无锁队列性能更好。
boost c++ lock-free queue vs shared queue
http://stackoverflow.com/questions/16275112/boost-c-lock-free-queue-vs-shared-queue
上面测试情况中,如果队列容量较小,那么读写两个线程大部分时间会是在相互等待;如果容量足够大,那么两个线程可以一路狂奔。
2,java中的阻塞和非阻塞实现
java中的LinkedBlockingQueue,其内部也是通过ReentrantLock和Condition实现阻塞的,上面的结论同样适用于java的相关API。
java中如果要使用无锁队列,可以使用ConcurrentLinkedQueue,其功能相当于boost::lockfree::queue,可支持多个生产者多个消费者的线程。
无锁和非阻塞当以当作同一种概念:阻塞肯定需要锁实现,而非阻塞可以无锁(wait-free)。
3,std::mutex和std::condition_variable的测试结果
将阻塞摸下的相关boost接口换成了std,结果还是一样的,区别不大。
修改代码如下:
#include <mutex> std::mutex m; std::condition_variable cv; void blocking_productor() { for (int i = 0; i < LOOP_COUNT; i++) { std::unique_lock<std::mutex> lk(m); cv.wait(lk, [] {return cb.size() < QUEUE_CAPACITY; }); cb.push_back(i); cv.notify_one(); } } void blocking_customer() { while (1) { std::unique_lock<std::mutex> lk(m); cv.wait(lk, [] {return cb.size() > 0; }); cb.pop_front(); cv.notify_one(); if (++count >= LOOP_COUNT) { break; } } }
输出结果:
[test_blocking]
QUEUE_CAPACITY:1000
cost:1557ms
count:10000000
[test_blocking]
QUEUE_CAPACITY:10000000
cost:1081ms
count:10000000
4,spsc_queue不一定是容量越大性能越好(20160420记)
我通过不同容量和不同循环次数发现:1亿次循环的情况下(相当于生产者填充1亿条消息),spsc_queue的容量超过1000w后(最大容量不能超过8000w),性能将开始下降。
另外,性能取决于生产者和消费者两个线程填充和取出消息的速度,我这里测试时两个线程比较极端,生产者填充消息的速度大概时20w条/1ms,这种情况下,spsc_queue需要的最佳容量为最总1000w;如果生产者和消费者填充和取出消息的速度越慢,则相对容量就越低,我测下来的情况是:1千万次循环,如果生产者和消费者的循环内部再添加一个循环次数为100的逻辑,则两者处理消息的速度均会大幅下降,此时spsc_queue的容量为1w即可发挥最佳性能。
所以结论是:如果想榨干CPU的极限性能,还是得从实际项目中具体的需求上取做测试。个人建议是,将spsc_queue的容量设置为1w,因为一般游戏的上层业务消息的逻辑也挺复杂,估计随便一个逻辑的耗时都能达到1ms,这样一个工作线程1秒内只能处理1000条消息(假设波动范围为1000~5000),那么1w的队列容量是够的(注意是单读单写)。
5,spsc_queue使用相关的优化(20160422记)
void nonblocking_customer() { int req_pop = 512; int* valarr = new int[req_pop]; int pop_size; while (1) { if (pop_size = spscq.pop(valarr, req_pop)) { for(int i = 0; i < pop_size; i++) { count++; } } else { Sleep(1); } if (count >= LOOP_COUNT) { break; } } }
之前是每次pop一个元素,现在改成一次pop尽可能多的元素。这样优化以后,1千万次累加,非阻塞队列的耗时能从之前的90毫秒降低到33毫秒。测试发现,获取元素的数组大小设置为512就可以达到最佳性能了。
相关推荐
它具有以下特性:当队列为空时,从队列中获取元素的操作会被阻塞;同样地,当队列满时,向队列中添加元素的操作也会被阻塞。这种特性使得`BlockingQueue`非常适合用于生产者-消费者模式的场景。 #### 2. ...
6. LinkedTransferQueue:基于链表的无界阻塞队列,支持高效的“传输”操作,可以在生产者和消费者之间直接传递元素。 7. LinkedBlockingDeque:基于链表的双向阻塞队列,既可以作为队列也可以作为栈使用。 在生产...
### BlockingQueue(阻塞队列)详解 #### 一、前言 随着现代软件系统对并发性能需求的不断提高,多线程编程技术逐渐成为开发人员不可或缺的技能之一。在Java平台中,`java.util.concurrent`包提供了丰富的工具来...
阻塞队列(BlockingQueue)是一种特殊的队列,它支持两个附加操作:阻塞的插入方法put和阻塞的移除方法take。BlockingQueue继承了Queue接口,是Java 5中加入的。 BlockingQueue常用方法示例: 1. add(E e):添加一...
阻塞队列是一种在多线程编程中广泛使用的并发数据结构,它在计算机科学和编程领域,特别是Java和C++等面向对象语言中扮演着重要角色。标题中的“支持多线程和泛型的阻塞队列”意味着我们讨论的是一个能够同时处理多...
BlockingQueue 是 Java 并发集合框架中的一种阻塞队列实现,提供了高效的并发操作和线程安全机制。它可以实现生产者-消费者模式,用于在多线程环境中实现高效的数据生产和消费。 BlockingQueue 的特点包括: * ...
BlockingQueue是Java并发包`java.util.concurrent`中的一个接口,它提供了在队列满时阻塞插入操作和队列空时阻塞删除操作的能力。这种设计模式被称为生产者-消费者模型,它有效地解决了线程间的同步问题,避免了不必...
在实际应用中,`BlockingQueue`和`BlockingDeque`常被用来实现工作队列、缓存、线程池等并发组件,例如`ThreadPoolExecutor`就利用`BlockingQueue`来存储等待执行的任务。 理解`BlockingQueue`和`BlockingDeque`的...
阻塞队列BlockingQueue是Java并发编程中一个重要的数据结构,它是线程安全的队列,主要用于生产者消费者模型中的数据交换。在Java的`java.util.concurrent`包中,提供了多种实现阻塞队列的类,如`ArrayBlockingQueue...
### 10、阻塞队列BlockingQueue 实战及其原理分析 #### 一、阻塞队列概述 阻塞队列(BlockingQueue)是Java语言中`java.util.concurrent`包下提供的一种重要的线程安全队列。它继承自`Queue`接口,并在此基础上...
总的来说,C++实现的跨平台`BlockingQueue`是一种强大的并发工具,通过合理的线程同步和阻塞机制,使得多线程程序能高效、稳定地运行。在理解其工作原理和实现细节后,我们可以灵活地应用到各种并发场景中,提升软件...
在Java编程中,"并发-线程池和阻塞队列"是两个核心概念,它们在多线程环境下处理任务调度和数据同步方面发挥着重要作用。线程池是一种管理线程资源的有效方式,而阻塞队列则常用于线程间通信和数据共享。 线程池...
首先,`BlockingQueue`是一个并发容器,它遵循先进先出(FIFO)原则,具有阻塞性质,当队列满时,生产者线程会被阻塞,直到有消费者取走元素;当队列空时,消费者线程会被阻塞,直到生产者放入新的元素。常用实现如`...
了解并熟练运用生产者/消费者模式以及像LinkedBlockingQueue这样的阻塞队列,对于优化多线程程序的性能和简化并发编程的复杂性至关重要。在阅读《xiongjiajia.iteye.com/blog/2325943》这篇博客文章时,你可以深入...
在Java编程语言中,阻塞队列是一种线程安全的数据结构,它在多线程并发控制中发挥着重要作用。阻塞队列的核心特性是当队列为空时,尝试...了解和掌握这些阻塞队列的特性和使用方法,对于优化并发程序的性能至关重要。
在Java编程中,`BlockingQueue`(阻塞队列)是一种重要的并发工具,它结合了队列的数据结构和线程同步机制。`BlockingQueue`接口位于`java.util.concurrent`包中,提供了线程安全的数据结构,可以用于实现生产者-...
Java中的阻塞队列BlockingQueue是一种并发编程中常用的工具,它实现了线程间的同步和通信。阻塞队列的核心特性在于当队列为空时,尝试获取元素的线程会被阻塞,直到其他线程添加元素;当队列满时,尝试添加元素的...
Java中的阻塞队列是一种基于同步原语的高级数据结构,它在多线程编程中扮演着重要角色,尤其在并发处理和优化系统资源利用率方面。阻塞队列结合了队列的数据结构与线程同步机制,使得生产者可以在队列满时被阻塞,而...
6.6 阻塞队列BlockingQueue 实战及其原 理分析一副本.mp4
6.7 阻塞队列BlockingQueue 实战及其原 理分析二副本.mp4