In the following program, when I access elements from the front of the circular buffer using *(begin_iterator + index)
I get a crash. But if I access the same elements using buffer[index]
, the crash is eliminated. (See the two commented lines below). Why does this happen?
#include <boost/circular_buffer.hpp>
#include <thread>
auto buffer = boost::circular_buffer<int>(5000);
void f1()
{
const auto N = 500;
while (true) {
// Make sure the buffer is never empty
if (buffer.size() < N+1) {
continue;
}
auto front = buffer.begin();
volatile auto j = int{};
for (auto i = 0; i < N; ++i) {
// j = *(front + i); // un-commenting this causes a crash
// j = buffer[i]; // un-commenting this doesn't
}
buffer.erase_begin(N);
}
}
void f2()
{
while (true) {
// Make sure the buffer is at most half-full.
if (buffer.size() > buffer.capacity() / 2) {
continue;
}
static auto k = 0;
buffer.push_back(k++);
}
}
int main()
{
auto t1 = std::thread{f1};
auto t2 = std::thread{f2};
t1.join();
}
You're experiencing undefined behavior because you're reading and modifying the same object unsynchronized from multiple threads.
Coding it one way or another might eliminate the crash, but the program is still wrong.
If we add a mutex, then there's no crash anymore:
#include <boost/circular_buffer.hpp>
#include <thread>
#include <mutex>
boost::circular_buffer<int> buffer(5000);
std::mutex mtx;
void f1()
{
const auto N = 500;
while (true) {
std::lock_guard<std::mutex> lock(mtx);
// Make sure the buffer is never empty
if (buffer.size() < N + 1) {
continue;
}
auto front = buffer.begin();
volatile auto j = int{};
for (auto i = 0; i < N; ++i) {
j = *(front + i); // is OK now
// j = buffer[i];
}
buffer.erase_begin(N);
}
}
void f2()
{
while (true) {
std::lock_guard<std::mutex> lock(mtx);
// Make sure the buffer is at most half-full.
if (buffer.size() > buffer.capacity() / 2) {
continue;
}
static auto k = 0;
buffer.push_back(k++);
}
}
int main()
{
auto t1 = std::thread{ f1 };
auto t2 = std::thread{ f2 };
t1.join();
}