文章目录
boost circular_buffer的特性及应用
boost库中的circular_bufer顾名思义是一个循环容器,官方文档的说明如下:
The circular_buffer is especially designed to provide fixed capacity storage. When its capacity is exhausted, newly inserted elements will cause elements to be overwritten, either at the beginning or end of the buffer (depending on what insert operation is used).
其capcity是固定的,不像标准容器中vector或list,他们的capcity是会根据策略动态增长的。当容量满了以后,插入一个元素时,会在容器的开头或结尾处删除一个元素。至于是头部还是尾部取决已加入的位置。
#include <iostream>
#include "boost/circular_buffer.hpp"
int main()
{
boost::circular_buffer<int> cb(3);
cb.push_back(1);
cb.push_back(2);
cb.push_back(3);
for (int i = 0; i < cb.size(); ++i)
{
std::cout << cb[i] << " ";
}
std::cout << std::endl;
//此时容量已满,下面新的push_back操作将在头部覆盖一个元素
cb.push_back(4);
for (int i = 0; i < cb.size(); ++i)
{
std::cout << cb[i] << " ";
}
std::cout << std::endl;
//下面的push_front操作将在尾部覆盖一个元素
cb.push_front(5);
for (int i = 0; i < cb.size(); ++i)
{
std::cout << cb[i] << " ";
}
system("pause");
}
boost circular_buffer的应用
官方文档描述了几种circular_buffer的应用场景
- Storage of the most recently received samples, overwriting the oldest as new samples arrive.
- As an underlying container for a bounded buffer
- A kind of cache storing a specified number of last inserted elements.
- Efficient fixed capacity FIFO (First In, First Out)
- Efficient fixed capacity LIFO (Last In, First Out) queue which removes the oldest (inserted as first) elements when full.
这里详细说明的是第二点应用场景。
circular_buffer实现的有界队列(消费生产者队列)
在多线程编程环境中通常会用到队列来分发数据或任务,circular_buffer非常适合来实现有界队列(消费生产者队列),其不但实现代码简单,效率也比用stl中的deque和list效率高。官方有一个例子,将circular_buffer与deque,list实现的有界队列进行了效率比较。
官方例子
下面是官方文档中基于circular_buffer实现的有界队列
template <class T>
class bounded_buffer {
public:
typedef boost::circular_buffer<T> container_type;
typedef typename container_type::size_type size_type;
typedef typename container_type::value_type value_type;
typedef typename boost::call_traits<value_type>::param_type param_type;
explicit bounded_buffer(size_type capacity) : m_unread(0), m_container(capacity) {}
void push_front(param_type item) {
boost::unique_lock<boost::mutex> lock(m_mutex);
m_not_full.wait(lock, boost::bind(&bounded_buffer<value_type>::is_not_full, this));
m_container.push_front(item);
++m_unread;
lock.unlock();
m_not_empty.notify_one();
}
void pop_back(value_type* pItem) {
boost::unique_lock<boost::mutex> lock(m_mutex);
m_not_empty.wait(lock, boost::bind(&bounded_buffer<value_type>::is_not_empty, this));
*pItem = m_container[--m_unread];
lock.unlock();
m_not_full.notify_one();
}
private:
bounded_buffer(const bounded_buffer&); // Disabled copy constructor
bounded_buffer& operator = (const bounded_buffer&); // Disabled assign operator
bool is_not_empty() const { return m_unread > 0; }
bool is_not_full() const { return m_unread < m_container.capacity(); }
size_type m_unread;
container_type m_container;
boost::mutex m_mutex;
boost::condition_variable m_not_empty;
boost::condition_variable m_not_full;
};
在官方的测试程序中,其效率比基于deque,list的实现高很多。注意上面pop_back的实现,取元素是通过下标去取并且在取出元素后也不用主动调用pop_back删除元素,这是因为circular_buffer在容量满的状态下,其内部实现会维持容量大小不变。反观基于deque,list的实现,如下pop_back方法的实现
void pop_back(value_type* pItem) {
boost::unique_lock<boost::mutex> lock(m_mutex);
m_not_empty.wait(lock, boost::bind(&bounded_buffer_deque_based<value_type>::is_not_empty, this));
*pItem = m_container.back();
m_container.pop_back();
lock.unlock();
m_not_full.notify_one();
}
由于deque,list的容量是动态增长的,所以在pop_back中必须手动删除元素。这种与circular_buffer实现上的差别造成的效率差别,在元素是对象时特别明显,基于deque,list实现的队列pop_back方法中,在*pItem = m_container.back();处会构造一次对象,在m_container.pop_back();处会析构一次对象。比较例子可以看官方测试例子中对元素类型为std::string时的结果。
官方文档中有对该例子效率的一个描述
The bounded buffer::pop_back() method does not remove the item but the item is left in the circular_buffer which then replaces it with a new one (inserted by a producer) when the circular_buffer is full. This technique is more effective than removing the item explicitly by calling the circular_buffer::pop_back() method of the circular_buffer.
This claim is based on the assumption that an assignment (replacement) of a new item into an old one is more effective than a destruction (removal) of an old item and a consequent inplace construction (insertion) of a new item.
circular_buffer在音视频系统中的应用
用于实现采集线程与编码线程间的队列
在音视频系统(软编软解)中,如果有一个场景是采集出来的视频数据直接编码发送出去(一般的视频编码速度是大大低于采集图像的速度,特别是在编码高分辨率,大码率的情况下。)此时采集与编码为串行流程,那么极有可能出现视频图像延迟现象的。原因是编码影响了采集,造成采集的图像数据特别慢(一般的DirectShow采集和V4l2采集,需要向系统注册回调函数,系统会定时调用回调将数据传给应用,如果在回调中直接编码视频数据就会看到图像延迟的情况)。此时需要实现采集和编码异步进行,那么可用通过队列来实现(采集线程与编码线程通过队列来交互数据),因为编码线程是肯定慢于采集线程的,此时使用消费生产者队列是不适合的。这种队列应具有如下特点:
- 长度固定并且不能过长,否则依然会出现延迟问题。
- 在该队列中数据满时,必须丢掉最老的视频图像数据。
这种队列非常适合用circular_buffer来实现,实现代码如下:
template <class T>
class DataQueue:public boost::noncopyable
{
public:
typedef boost::circular_buffer<T> BufferType;
typedef typename BufferType::size_type size_type;
typedef typename BufferType::value_type value_type;
explicit DataQeque(size_type size) :m_Qeque(size)
{}
void Put(const value_type &data)
{
{
std::lock_guard<std::mutex> lock(m_mutex);
m_Qeque.push_back(data);
}
m_cndNotEmpty.notify_one();
}
void Get(value_type &data)
{
std::unique_lock<std::mutex> lock(m_mutex);
while (m_Qeque.empty())
{
m_cndNotEmpty.wait(lock);
}
data = m_Qeque.front();
}
private:
BufferType m_Qeque;
std::mutex m_mutex;
std::condition_variable m_cndNotEmpty;
};
如果在编码线程慢于采集线程可以确定的情况下(绝大多数情况即使不是编码高清码流,编码线程效率也是小于采集线程的),m_cndNotEmpty这个条件变量可以不用要。
使用这样的队列有一个“副作用”,会出现跳帧的情况,这些因为老的视频图像被丢弃造成的,有一种情况在编码线程效率大大低于采集线程,如果队列长度设置的过小,此时会出现严重的跳帧,将队列大小加大可以缓解频繁的跳帧问题。
用于实现解码线程与渲染线程间的队列
这个场景其实跟上面描述的场景一样,渲染线程(比如用opengl渲染)慢于解码线程,同样会出现上述情况,此时用上述队列同样可以应用于该场景。
以上。
资料:
http://www.boost.org/doc/libs/1_66_0/doc/html/circular_buffer.html
http://www.boost.org/doc/libs/1_66_0/libs/circular_buffer/test/bounded_buffer_comparison.cpp