#include <mutex>
#include <memory>
#include <condition_variable>
#include <chrono>
#include <thread> // std::this_thread::sleep_for
// | tail
// V push here
// +----+ +----+ +----+ +----+ +----+
// | |<--| |<--| |<--| |<--| |<-- head
// +----+ +----+ +----+ +----+ +----+ pop here
template<typename T>
class queue_1t
{
private:
struct node
{
T data;
std::unique_ptr<node> next;
node(T data_):
data(std::move(data_))
{}
};
std::unique_ptr<node> head;
node* tail;
public:
queue_1t() {}
queue_1t(const queue_1t& other)=delete;
queue_1t& operator=(const queue_1t& other)=delete;
bool empty() const
{
return !head;
}
std::shared_ptr<T> try_pop()
{
if(!head)
{
return std::shared_ptr<T>();
}
std::shared_ptr<T> const res( std::make_shared<T>(std::move(head->data)));
std::unique_ptr<node> const old_head=std::move(head);
head=std::move(old_head->next);
return res;
}
void push(T new_value)
{
std::unique_ptr<node> p(new node(std::move(new_value)));
node* const new_tail=p.get();
if(tail)
{
tail->next=std::move(p);
}
else
{
head=std::move(p);
}
tail=new_tail;
}
};
template<typename T>
class threadsafe_queue
{
private:
struct node
{
std::shared_ptr<T> data;
std::unique_ptr<node> next;
};
std::mutex head_mutex;
std::unique_ptr<node> head;
mutable std::mutex tail_mutex;
node* tail;
node* get_tail() const
{
//std::lock_guard<std::mutex> tail_lock(tail_mutex); // !!!
return tail;
}
#if 1 // correct
std::unique_ptr<node> pop_head()
{
std::lock_guard<std::mutex> head_lock(head_mutex);
if(head.get()==get_tail()) // get_tail() is inside the lock
{
return nullptr;
}
std::unique_ptr<node> old_head=std::move(head);
head=std::move(old_head->next);
return old_head;
}
#else // incorrect implementation
std::unique_ptr<node> pop_head()
{
node* const old_tail=get_tail(); // !!
std::lock_guard<std::mutex> head_lock(head_mutex);
if(head.get()==old_tail)
{
return nullptr;
}
std::unique_ptr<node> old_head=std::move(head);
head=std::move(old_head->next);
return old_head;
}
#endif
public:
threadsafe_queue(): head(new node),tail(head.get()) {}
threadsafe_queue(const threadsafe_queue& other)=delete;
threadsafe_queue& operator=(const threadsafe_queue& other)=delete;
std::shared_ptr<T> try_pop()
{
std::unique_ptr<node> old_head=pop_head();
return old_head?old_head->data:std::shared_ptr<T>();
}
void push(T new_value)
{
// concurrency prepare data
std::shared_ptr<T> new_data( std::make_shared<T>(std::move(new_value)) );
std::unique_ptr<node> p(new node);
node* const new_tail=p.get();
// now lock
std::lock_guard<std::mutex> tail_lock(tail_mutex);
#if 0 // both are correct
node* old_tail = tail;
tail=new_tail;
old_tail->data=new_data;
old_tail->next=std::move(p);
#else
tail->data=new_data;
tail->next=std::move(p);
tail=new_tail;
//std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );
#endif
}
bool empty() const // single threaded - just for testing
{
return (head.get()==get_tail());
}
};