47. Double buffer

The problem described here is a typical double buffering situation. Double buffering is the most common case of multiple buffering, which is a technique that allows a reader to see a complete version of the data and not a partially updated version produced by a writer. This is a common technique – especially in computer graphics – for avoiding flickering.

In order to implement the requested functionality, the buffer class that we should write must have two internal buffers: one that contains temporary data being written, and another one that contains completed (or committed) data. Upon the completion of a write operation, the content of the temporary buffer is written in the primary buffer. For the internal buffers, the implementation below uses std::vector. When the write operation completes, instead of copying data from one buffer to the other, we just swap the content of the two, which is a much faster operation. Access to the completed data is provided with either the read() function, which copies the content of the read buffer into a designated output, or with direct element access (overloaded operator[]). Access to the read buffer is synchronized with an std::mutex to make it safe to read from one thread while another is writing to the buffer:

template <typename T>
class double_buffer
{
typedef T value_type;
typedef T& reference;
typedef T const & const_reference;
typedef T* pointer;
public:
explicit double_buffer(size_t const size) :
rdbuf(size), wrbuf(size)
{}

size_t size() const noexcept { return rdbuf.size(); }

void write(T const * const ptr, size_t const size)
{
std::unique_lock<std::mutex> lock(mt);
auto length = std::min(size, wrbuf.size());
std::copy(ptr, ptr + length, std::begin(wrbuf));
wrbuf.swap(rdbuf);
}


template <class Output>
void read(Output it) const
{
std::unique_lock<std::mutex> lock(mt);
std::copy(std::cbegin(rdbuf), std::cend(rdbuf), it);
}

pointer data() const
{
std::unique_lock<std::mutex> lock(mt);
return rdbuf.data();
}

reference operator[](size_t const pos)
{
std::unique_lock<std::mutex> lock(mt);
return rdbuf[pos];
}

const_reference operator[](size_t const pos) const
{
std::unique_lock<std::mutex> lock(mt);
return rdbuf[pos];
}

void swap(double_buffer other)
{
std::swap(rdbuf, other.rdbuf);
std::swap(wrbuf, other.wrbuf);
}

private:
std::vector<T> rdbuf;
std::vector<T> wrbuf;
mutable std::mutex mt;
};

The following is an example of how this double buffer class can be used for both writing and reading by two different entities:

template <typename T>
void print_buffer(double_buffer<T> const & buf)
{
buf.read(std::ostream_iterator<T>(std::cout, " "));
std::cout << std::endl;
}

int main()
{
double_buffer<int> buf(10);

std::thread t([&buf]() {
for (int i = 1; i < 1000; i += 10)
{
int data[] = { i, i + 1, i + 2, i + 3, i + 4,
i + 5, i + 6,i + 7,i + 8,i + 9 };
buf.write(data, 10);

using namespace std::chrono_literals;
std::this_thread::sleep_for(100ms);
}
});

auto start = std::chrono::system_clock::now();
do
{
print_buffer(buf);

using namespace std::chrono_literals;
std::this_thread::sleep_for(150ms);
} while (std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now() - start).count() < 12);

t.join();
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset