I'm pretty new to multithreading. The code below is a simple implementation of a single consumer/single producer circular buffer that does not use any locking, that I wrote for fun.
The question is, will this kind of container work in actual real world situation or would it crash miserably because of some data race that I have not noticed?
#include <array>
#include <memory>
#include <atomic>
#include <stdexcept>
#include <iostream>
#include <thread>
#include <random>
template<typename T, int S=5>
class CircularBuffer{
public:
CircularBuffer():start(0),end(0){
}
bool AddElement(std::unique_ptr<T> && obj,bool block=true){
int end_idx=end.load();
if(block)
while(end_idx+1==start.load());//Busy wait until there is place
else if(end_idx+1==start.load())
return false;
buffer[end_idx++]=std::move(obj);
if(end_idx==buffer.size())
end.store(0);
else
end.store(end_idx);
return true;
}
std::unique_ptr<T> GetElement(bool block=true){
int start_idx=start.load();
if(block)
while(end.load()==start_idx);
else if(end.load()==start_idx)
return std::unique_ptr<T>();
auto ret_obj=std::move(buffer[start_idx]);
start_idx++;
if(start_idx==buffer.size())
start.store(0);
else
start.store(start_idx);
return ret_obj;
}
private:
std::array<std::unique_ptr<T>,S+1> buffer;
std::atomic<int> start;
std::atomic<int> end;
};
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0,500);
void Producer(CircularBuffer<int> * buffer){
int i=0;
while(i<20){
std::this_thread::sleep_for(std::chrono::milliseconds(dis(gen)));
auto val=dis(gen);
std::cout << "Adding " << val <<std::endl;
buffer->AddElement(std::unique_ptr<int>(new int(val)));
i++;
}
}
void Consumer(CircularBuffer<int> * buffer){
int i=0;
while(i<20){
std::this_thread::sleep_for(std::chrono::milliseconds(dis(gen)));
std::cout << "Got " << *buffer->GetElement() << std::endl;
i++;
}
}
int main(){
CircularBuffer<int> buffer;
std::thread t[2];
t[0]=std::thread(Producer,&buffer);
t[1]=std::thread(Consumer,&buffer);
t[0].join();
t[1].join();
}