Этот пример показывает несколько функций, включая суммирование всех действительных значений.
#include <boost/circular_buffer.hpp>
#include <numeric>
#include <assert.h>
int main(int , char* [])
{
boost::circular_buffer<int> cb(3);
assert(cb.capacity() == 3);
assert(cb.size() == 0);
assert(cb.empty());
cb.push_back(1);
cb.push_back(2);
assert(cb[0] == 1);
assert(cb[1] == 2);
assert(!cb.full());
assert(cb.size() == 2);
assert(cb.capacity() == 3);
cb.push_back(3);
cb.push_back(4);
int sum = std::accumulate(cb.begin(), cb.end(), 0);
assert(sum == 9);
assert(cb[0] == 2);
assert(cb[1] == 3);
assert(cb[2] == 4);
assert(*cb.begin() == 2);
assert(cb.front() == 2);
assert(cb.back() == 4);
assert(cb.full());
assert(cb.size() == 3);
assert(cb.capacity() == 3);
return 0;
}
circular_buffer
имеет вместимость три int
. Поэтому размер буфера никогда не превысит трех. std::accumulate
алгоритм оценивает сумму хранимых элементов. Семантика circular_buffer
может быть выведена из утверждений.
Вы можете увидеть полный пример код на circular_buffer_sum_example.cpp.
Ограниченный буфер обычно используется в режиме производителя-потребителя: потоки производителей производят товары и хранят их в контейнере, а потребительские потоки удаляют эти предметы и обрабатывают их. Ограниченный буфер должен гарантировать, что
- производители не вставляют предметы в контейнер, когда контейнер заполнен,
- потребители не пытаются удалить предметы, когда контейнер пуст,
- каждый произведенный продукт потребляется ровно одним потребителем.
Этот пример показывает, как circular_buffer
может быть использован в качестве основного контейнера ограниченного буфера.
#include <boost/circular_buffer.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition.hpp>
#include <boost/thread/thread.hpp>
#include <boost/call_traits.hpp>
#include <boost/bind.hpp>
#include <boost/timer/timer.hpp>
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(typename boost::call_traits<value_type>::param_type item)
{
boost::mutex::scoped_lock 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::mutex::scoped_lock 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&);
bounded_buffer& operator = (const bounded_buffer&);
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 m_not_empty;
boost::condition m_not_full;
};
The bounded_buffer полагается на Boost.Thread и Boost.Bind библиотеки и Boost.call_traits .
Метод push_front()
называется нитью производителя, чтобы вставить новый элемент в буфер. Метод запирает мутекс и ждет, пока не появится место для нового элемента. (Мутекс разблокирован на этапе ожидания и должен быть восстановлен, когда условие выполняется.) Если в буфере есть пространство, исполнение продолжается, и метод вставляет элемент в конце circular_buffer
. Затем он увеличивает количество нечитаемых предметов и разблокирует мутекс (в случае, если исключение выбрасывается до того, как мутекс будет разблокирован, мутекс автоматически разблокируется деструктором масштабируемого_блока). Наконец, метод уведомляет одну из потребительских потоков, ожидающих включения нового элемента в буфер.
Метод pop_back()
называется потребительской нитью, чтобы прочитать следующий элемент из буфера. Метод блокирует мутекс и ждет, пока в буфере не появится непрочитанный элемент. Если есть по крайней мере один непрочитанный элемент, метод сокращает количество нечитаемых предметов и читает следующий пункт из circular_buffer
. Затем он открывает мутекс и уведомляет одну из потоков производителя, ожидающих, что буфер освободит пространство для следующего элемента.
bounded buffer::pop_back()
метод не удаляет пункт, но элемент остается в циркулярном баффере, который затем заменяет его новым (включенным производителем) когда циркуляр_buffer заполнен. Этот метод более эффективен, чем удаление элемента явно, позвонив circular_buffer::pop_back()
метод circular_buffer.
Эта претензия основана на предположении о том, что присвоение (замещение) нового элемента в старый более эффективно, чем уничтожение (удаление) старого элемента и последующее строительство (вторжение) нового элемента.
Для сравнения ограниченных буферов на основе различных контейнеров компилируются и запускаются bounded_buffer_comparison.cpp. Тест должен показать ограниченный буфер на основе circular_buffer
наиболее эффективен, за которым следует std::deque
на основе ограниченного буфера. (В действительности результат может отличаться иногда, потому что тест всегда зависит от внешних факторов, таких как непосредственная нагрузка процессора.)
Вы можете увидеть полный тестовый код на bounded_buffer_comparison.cpp, и пример вывода
Description: Autorun "J:\Cpp\Misc\Debug\bounded_buffer_comparison.exe"
bounded_buffer<int> 5.15 s
bounded_buffer_space_optimized<int> 5.71 s
bounded_buffer_deque_based<int> 15.57 s
bounded_buffer_list_based<int> 17.33 s
bounded_buffer<std::string> 24.49 s
bounded_buffer_space_optimized<std::string> 28.33 s
bounded_buffer_deque_based<std::string> 29.45 s
bounded_buffer_list_based<std::string> 31.29 s
...