Below is my honest attempt to write thread safe queue using C++11 features. Please review it and provide your comments.
#include <queue>
#include <condition_variable>
#include <atomic>
#include <iostream>
template <typename T>
class ThreadSafeQueue
{
std::queue<T> m_queue;
std::mutex m_mutex;
public :
void Push(const T& data)
{
std::lock_guard<std::mutex> lock(m_mutex);
m_queue.push(data);
}
// Universal reference
template <typename ...Args>
void Emplace(Args&&... args)
{
std::lock_guard<std::mutex> lock(m_mutex);
m_queue.emplace(std::forward<Args>(args)...);
}
void PopFront(T& data)
{
std::lock_guard<std::mutex> lock(m_mutex);
data = m_queue.front();
m_queue.pop();
}
void PopFrontNoLock(T& data)
{
data = m_queue.front();
m_queue.pop();
}
size_t Size()
{
std::lock_guard<std::mutex> lock(m_mutex);
return m_queue.size();
}
bool Empty()
{
std::lock_guard<std::mutex> lock(m_mutex);
return m_queue.empty();
}
bool EmptyNoLock()
{
return m_queue.empty();
}
std::mutex& GetMutex()
{
return m_mutex;
}
};
template <typename T>
class ReaderWriterQueue
{
ThreadSafeQueue<T> m_safeQueue{};
std::condition_variable m_condVar{};
std::atomic<bool> m_writeCompleted{};
public :
void Push(const T& data)
{
m_safeQueue.Push(data);
m_condVar.notify_one();
}
// Universal reference
template <typename ...Args>
void Emplace(Args&&... args)
{
m_safeQueue.Emplace(std::forward<Args>(args)...);
m_condVar.notify_one();
}
void PopFront(T& data)
{
std::unique_lock<std::mutex> lock(m_safeQueue.GetMutex());
m_condVar.wait(lock, [this] {return (!m_safeQueue.EmptyNoLock() || m_writeCompleted); });
m_safeQueue.PopFrontNoLock(data);
}
void NotifyWriteEnd()
{
m_writeCompleted = true;
}
bool IsWriteFinished() const
{
return m_writeCompleted;
}
bool Empty()
{
return m_safeQueue.Empty();
}
};
Driver code to test thread safe queue:
void ProduceData(ReaderWriterQueue<int>& queue)
{
int i = 0;
while (i < 10)
{
queue.Emplace(i);
i++;
}
queue.NotifyWriteEnd();
std::cout << "Finished Writing" << std::endl;
}
void ReadData(ReaderWriterQueue<int>& queue)
{
while (!queue.IsWriteFinished() || !queue.Empty())
{
int waitTime;
queue.PopFront(waitTime);
std::cout << "popped " << waitTime << "\n";
std::this_thread::sleep_for(std::chrono::seconds(waitTime));
}
std::cout << "Finished Reading" << std::endl;
}
int main()
{
ReaderWriterQueue<int> rwQueue;
std::thread writer(ProduceData, std::ref(rwQueue));
const int readerThreadCount = 2;
std::thread readers[readerThreadCount];
for (int count=0; count < readerThreadCount; count++)
{
std::thread reader(ReadData, std::ref(rwQueue));
readers[count] = std::move(reader);
}
if (writer.joinable())
{
writer.join();
}
for (int count = 0; count < readerThreadCount; count++)
{
if (readers[count].joinable())
{
readers[count].join();
}
}
return 0;
}