#ifndef CPP11_BLOCKINGQUEUE_H #define CPP11_BLOCKINGQUEUE_H /////////////////////////////////////////////////////////////// // Cpp11-BlockingQueue.h - Thread-safe Blocking Queue // // ver 1.3 // // Jim Fawcett, CSE687 - Object Oriented Design, Spring 2015 // /////////////////////////////////////////////////////////////// /* * Package Operations: * ------------------- * This package contains one thread-safe class: BlockingQueue. * Its purpose is to support sending messages between threads. * It is implemented using C++11 threading constructs including * std::condition_variable and std::mutex. The underlying storage * is provided by the non-thread-safe std::queue. * * Required Files: * --------------- * Cpp11-BlockingQueue.h * * Build Process: * -------------- * devenv Cpp11-BlockingQueue.sln /rebuild debug * * Maintenance History: * -------------------- * ver 1.3 : 04 Mar 2016 * - changed behavior of front() to throw exception * on empty queue. * - added comment about std::unique_lock in deQ() * ver 1.2 : 27 Feb 2016 * - added front(); * - added move ctor and move assignment * - deleted copy ctor and copy assignment * ver 1.1 : 26 Jan 2015 * - added copy constructor and assignment operator * ver 1.0 : 03 Mar 2014 * - first release * */ #include #include #include #include #include #include #include template class BlockingQueue { public: BlockingQueue() {} BlockingQueue(BlockingQueue&& bq); BlockingQueue& operator=(BlockingQueue&& bq); BlockingQueue(const BlockingQueue&) = delete; BlockingQueue& operator=(const BlockingQueue&) = delete; T deQ(); void enQ(const T& t); T& front(); void clear(); size_t size(); private: std::queue q_; std::mutex mtx_; std::condition_variable cv_; }; //----< move constructor >--------------------------------------------- template BlockingQueue::BlockingQueue(BlockingQueue&& bq) // need to lock so can't initialize { std::lock_guard l(mtx_); q_ = bq.q_; while (bq.q_.size() > 0) // clear bq bq.q_.pop(); /* can't copy or move mutex or condition variable, so use default members */ } //----< move assignment >---------------------------------------------- template BlockingQueue& BlockingQueue::operator=(BlockingQueue&& bq) { if (this == &bq) return *this; std::lock_guard l(mtx_); q_ = bq.q_; while (bq.q_.size() > 0) // clear bq bq.q_.pop(); /* can't move assign mutex or condition variable so use target's */ return *this; } //----< remove element from front of queue >--------------------------- template T BlockingQueue::deQ() { std::unique_lock l(mtx_); /* This lock type is required for use with condition variables. The operating system needs to lock and unlock the mutex: - when wait is called, below, the OS suspends waiting thread and releases lock. - when notify is called in enQ() the OS relocks the mutex, resumes the waiting thread and sets the condition variable to signaled state. std::lock_quard does not have public lock and unlock functions. */ if(q_.size() > 0) { T temp = q_.front(); q_.pop(); return temp; } // may have spurious returns so loop on !condition while (q_.size() == 0) cv_.wait(l, [this] () { return q_.size() > 0; }); T temp = q_.front(); q_.pop(); return temp; } //----< push element onto back of queue >------------------------------ template void BlockingQueue::enQ(const T& t) { { std::unique_lock l(mtx_); q_.push(t); } cv_.notify_one(); } //----< peek at next item to be popped >------------------------------- template T& BlockingQueue::front() { std::lock_guard l(mtx_); if(q_.size() > 0) return q_.front(); throw std::exception("attempt to deQue empty queue"); } //----< remove all elements from queue >------------------------------- template void BlockingQueue::clear() { std::lock_guard l(mtx_); while (q_.size() > 0) q_.pop(); } //----< return number of elements in queue >--------------------------- template size_t BlockingQueue::size() { std::lock_guard l(mtx_); return q_.size(); } #endif