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

Posted

技术标签:

【中文标题】C++中对队列的多线程访问【英文标题】:Multi-threaded access to a Queue in C++ 【发布时间】:2019-12-23 11:12:27 【问题描述】:

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

 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();

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

【问题讨论】:

请展示您实际使用互斥锁的代码和您的两个线程。每个线程在持有互斥锁时做了多少工作? 互斥锁被锁定时你在做什么?您何时何地锁定它?你如何使用这个类和它包含的队列?请尝试创建minimal reproducible example 向我们展示,否则将很难为您提供帮助。也请刷新how to ask good questions,以及this question checklist。 添加了更多代码 您似乎只保护了对 addToQueue 内部队列的访问,该队列不提供任何同步,因此由于竞争条件您获得了 UB。 但是我有一个全局互斥变量 【参考方案1】:

在您的 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 毫秒获取一次锁定,并且只需要足够长的时间将 q 替换为 temp。然后它可以按照自己的节奏打印内容。

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

【讨论】:

for 循环对我不起作用。我认为您不能以这种方式遍历队列.. 你是对的。我编辑了我的答案以使用 frontpop 而不是 for-each 循环。 谢谢!那为我完成了工作! +1【参考方案2】:

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

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

鉴于您的代码在哪里,并且问题是众所周知的。我建议你应该开始寻找现有的代码。从扫描阅读this 看起来像是一个合理的概述。尤其是“有界缓冲区”部分。 Boost 有一些不使用互斥锁的实现。这比您似乎需要的更先进。我不会为您提供建议,但其他人可能会对此感兴趣。 https://www.boost.org/doc/libs/1_54_0/doc/html/boost/lockfree/queue.html

【讨论】:

以上是关于C++中对队列的多线程访问的主要内容,如果未能解决你的问题,请参考以下文章

我的 MFC C++ .dll 的多线程

具有非空函数的多线程

c++ windows中客户端服务器编程中的多线程

C++中的多线程矩阵乘法

如何实现springMVC的多线程并发?

如何实现springMVC的多线程并发?