多线程访问 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
所以基本上我有两个线程:一个生成字符串的组合并将它们附加到作为 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