Creating a Blocking Queue
有时这种
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | #include <thread> using std::thread; #include <mutex> using std::mutex; #include <iostream> using std::cout; using std::endl; #include <queue> using std::queue; #include <string> using std::string; using std::to_string; #include <functional> using std::ref; template <typename T> class BlockingQueue { private: mutex mutex_; queue< T > queue_; public: T pop() { this->mutex_.lock(); T value = this->queue_.front(); this->queue_.pop(); this->mutex_.unlock(); return value; } void push(T value) { this->mutex_.lock(); this->queue_.push(value); this->mutex_.unlock(); } bool empty() { this->mutex_.lock(); bool check = this->queue_.empty(); this->mutex_.unlock(); return check; } }; void fillWorkQueue(BlockingQueue<string>& workQueue) { int size = 40000; for(int i = 0; i < size; i++) workQueue.push(to_string(i)); } void doWork(BlockingQueue<string>& workQueue) { while(!workQueue.empty()) { workQueue.pop(); } } void multiThreaded() { BlockingQueue<string> workQueue; fillWorkQueue(workQueue); thread t1(doWork, ref(workQueue)); thread t2(doWork, ref(workQueue)); t1.join(); t2.join(); cout <<"done\ "; } int main() { cout << endl; // Multi Threaded cout <<"multiThreaded\ "; multiThreaded(); cout << endl; } |
请看这里:
我从空 std 容器的 front() 中得到什么?
如果你在一个空容器上调用
试试:
1 2 3 4 5 6 7 8 9 10 11 12 | T pop() { this->mutex_.lock(); T value; if( !this->queue_.empty() ) { value = this->queue_.front(); // undefined behavior if queue_ is empty // may segfault, may throw, etc. this->queue_.pop(); } this->mutex_.unlock(); return value; } |
注意:由于原子操作对这种队列很重要,我建议更改 API:
1 | bool pop(T &t); // returns false if there was nothing to read. |
更好的是,如果您确实在重要的地方使用它,您可能希望在删除之前标记正在使用的项目以防失败。
1 2 3 | bool peekAndMark(T &t); // allows one"marked" item per thread void deleteMarked(); // if an item is marked correctly, pops it. void unmark(); // abandons the mark. (rollback) |
问题应该出在这里:
1 2 3 | while(!itemQueue.empty()) { itemQueue.pop(); } |
您在检查剩余值时保留互斥锁,然后释放互斥锁,可能会发生另一个线程执行,发现剩余值并将其弹出。在最坏的情况下,之后不会留下任何项目,并且第一个线程会尝试在没有留下任何元素的情况下弹出。
解决方案是在同一节中的内部队列上进行前/弹出调用,而不是在同一锁定节中检查空,然后将始终定义行为。
另一个建议是在使用互斥锁时使用
考虑到这两个建议,您的
1 2 3 4 5 6 7 8 9 10 | T pop() { std::lock_guard lock(this->mutex_); //mutex_ is locked T value; if( !this->queue_.empty() ) { value = this->queue_.front(); this->queue_.pop(); } return value; } //mutex_ is freed |