多线程访问 C++ 中的队列

Multi-threaded access to a Queue in C++

所以基本上我有两个线程:一个生成字符串的组合并将它们附加到作为 class 成员的队列中。 第二个线程应该将该队列中的所有内容打印到文件中。如果队列为空,我应该等到有另一个元素等等。

 std::mutex m;
Class c{
  std::queue<std::string> q;
  std::ofstream file;

  void print(std::string str){
    file << str << "\n";
  } // Print to file

  void generate(){
    str = "abc" // do stuff
    q.push(str);
  }

}

当我使用 std::mutex 时,程序的性能变得非常糟糕。 我想我需要一个管理队列访问的函数,以便我可以同时写入和打印。 我该怎么做?

void Generator::print() {
    int c = 0;
    while (c < totalN){
        if(!printQueue.empty()){
            fileStream << printQueue.front() << '\n';
            printQueue.pop();
            c++;
        }
    }
}

void Generator::getCombinations(unsigned long start, unsigned long end) {
// Fill with dummy elements
std::string comb(length, ' ');
std::string temp(length, ' ');
auto total_n = static_cast<unsigned long>(std::pow(elementCount, length));
for (auto i = start; i < end; ++i) {
    auto n = i;
    for (size_t j = 0; j < length; ++j) {
        comb[comb.size() - j - 1] = charPool[n % elementCount];
        n /= elementCount;
    }
    temp = comb;
    for (auto f : tasks) (this->*f)(temp); // Call addQueue func
}
}


void Generator::addToQueue(std::string &str) {
    m.lock();
    printQueue.push(str);
    m.unlock();
}

出于某种原因,我遇到了错误访问错误,因为打印函数试图从空队列中打印一些东西,这对我来说似乎是不可能的,因为这部分代码仅在队列不为空时才执行...

在您的 Generator::Print 函数中,您最好将共享队列换成一个空队列,然后使用其中的内容:

void Generator::print() {
  int todo = totalN;
  while (todo) {
    std::this_thread::sleep_for(500ms);
    std::queue<std::string> temp;
    { // Lock only taken for this section
      std::lock_guard<std::mutex> lock(m);
      std::swap(temp, q);
    }
    todo -= temp.size();
    while (!temp.empty()) {
      fileStream << temp.front() << '\n';
      temp.pop();
    }
  }
}

这最多每 500 毫秒获取一次锁,并且只够用 temp 替换 q。然后它可以按照自己的节奏打印内容。

请注意,如果生成比打印慢得多,您可以一次只弹出一个而不是像我在这里那样交换队列。

这是一个称为 producer/consumer queue 的标准问题。
C++ 中当前开箱即用的解决方案是 condition_variable。如果您遵循 link,您将找到一个示例解决方案。注意您缺少的一些功能

  • 始终 lock/unlock 通过 std::lock_guard 或 std::unique_lock。
  • 如果速度很重要,请使用条件变量来控制每个线程何时唤醒或休眠。
  • 对结构的每次访问都必须同步。这包括 pushing/popping 甚至 const 函数,例如调用 empty.

鉴于您的代码所在的位置,并且该问题众所周知。我建议您应该开始寻找现有代码。从扫描阅读 this 看起来像是一个合理的概述。特别是,请查看 "bounded buffer" 部分。
Boost 有一些不使用互斥锁的实现。这比您似乎需要的更高级。我不会建议您这样做,但其他人可能会对此感兴趣。 https://www.boost.org/doc/libs/1_54_0/doc/html/boost/lockfree/queue.html