【问题标题】:Creating a Blocking Queue创建阻塞队列
【发布时间】:2014-05-14 17:55:29
【问题描述】:

有时BlockingQueue 的这种实现和执行可以正常工作。有时它会出现段错误。知道为什么吗?

#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\n";
}

int main() {
    cout << endl;

    // Multi Threaded
    cout << "multiThreaded\n";
    multiThreaded();
    cout << endl;
}

【问题讨论】:

  • 如果出现段错误,我想你可以得到它发生的代码行?了解一下可能会有所帮助...
  • 如果你检查itemQueue是否为空,然后让其他线程做一些工作,然后pop()一个项目会发生什么?
  • 这个问题有足够的代码,任何人都可以尝试一下,自己看看问题出在哪里。也没有太多多余的代码,所以离教科书SSCCE不远了,当然可以回答。
  • @Flexo 我很难相信“这个程序的错误在哪里?”可能是个好问题。
  • 很高兴在metachat 上进一步解释我对此的想法,如果您愿意,但请避免在 cmets 中聊天。

标签: c++ multithreading c++11 blockingqueue


【解决方案1】:

看这里:

What do I get from front() of empty std container?

如果您在空容器上调用.front(),会发生不好的事情,最好先检查.empty()

试试:

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:

bool pop(T &t);  // returns false if there was nothing to read.

更好的是,如果您确实在重要的地方使用它,您可能希望在删除之前标记正在使用的项目以防失败。

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)

【讨论】:

  • @πάνταῥεῖ 实际上,我只是避开了std:: 和 C++。宁可使用 C++/CLI、C#、C、Java 以及除了 vanilla C++ 和 STL 之外的任何东西。任何将异常定义为不安全的错误功能的语言都值得在我的书中忽略。我的意思是谁实现了一个读取为段错误的空队列?!
  • 在 c++ 中以这种方式实现锁定/解锁序列很容易出错(并且可能在其他语言中),因为它不是异常安全的。您可以使用 c++ 标准提供的std:lock_guard 惯用语之一,或者在必要时轻松滚动您自己的习惯用法!
  • @ebyrob 我同意 API 更改。这是避免“使用什么作为默认值”问题的一个很好的解决方案。
  • @ebyrob this-&gt;mutex_.lock()/this-&gt;mutex_.unlock(); 你不应该直接这样做!请改用适当的作用域构造/破坏习语!
  • 好的@πάνταῥεῖ。我同意使用unique_lock&lt;mutex&gt; lock(this-&gt;mutex_) 会产生更易读的代码。
【解决方案2】:

问题应该出在这里:

while(!itemQueue.empty()) {
    itemQueue.pop();
}

当检查剩余值时保留互斥锁,然后释放互斥锁,可能会执行另一个线程,发现剩余值并将其弹出。在最坏的情况下,之后不会留下任何项目,并且第一个线程会尝试在没有留下任何元素的情况下弹出。

解决方案是在同一节中的内部队列上进行前/弹出调用,而不是在同一锁定节中检查空,然后始终定义行为。

另一个建议是在使用互斥锁时使用std::lock_guard,因为它提高了可读性并确保无论发生什么情况都会释放互斥锁。

考虑到这两个建议,您的 pop 方法可能如下所示:

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

【讨论】:

  • +1 只需提及标准或自定义的自动储物柜机制,您的答案就应该是完美的,尽管是低质量的问题!
  • 如果 itemQueue 为空会怎样?段错误、异常或未定义的行为。我相信std::queue.front() 的行为是这里的惊喜,而不是线程相互抢占。如果你明确提到它,我就不会费心回答了。
  • @ebyrob 我说的是他实现的pop方法。
  • @Theolodis T value; 认真的吗?好像你复制粘贴了我的代码。
  • 你的意思是std::lock_guard&lt;std::mutex&gt; lock(this-&gt;mutex_);
猜你喜欢
  • 2014-10-16
  • 2010-10-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-07
相关资源
最近更新 更多