C++ C++;并发队列内存泄漏
考虑下面的程序,它实现了单个使用者、多个生产者并发队列 在一个消费者,一个生产者的情况下运行良好 然而,放置第二个消费者(不评论下面的行)会导致内存泄漏,我不明白为什么 使用队列的T=std::shared_ptr并修改pop以返回一个shared_ptr修复了内存泄漏,那么我在下面的代码中缺少了什么呢C++ C++;并发队列内存泄漏,c++,multithreading,concurrency,C++,Multithreading,Concurrency,考虑下面的程序,它实现了单个使用者、多个生产者并发队列 在一个消费者,一个生产者的情况下运行良好 然而,放置第二个消费者(不评论下面的行)会导致内存泄漏,我不明白为什么 使用队列的T=std::shared_ptr并修改pop以返回一个shared_ptr修复了内存泄漏,那么我在下面的代码中缺少了什么呢 #include <functional> #include <iostream> #include <mutex> #include <thread&
#include <functional>
#include <iostream>
#include <mutex>
#include <thread>
template<typename T>
class Queue {
private:
static constexpr unsigned mSize = 256; //power of two only
static constexpr unsigned mRoundRobinMask = mSize - 1;
static const T mEmpty;
T mData[mSize];
std::mutex mtx;
unsigned mReadP = 0;
unsigned mWriteP = 0;
public:
const T pop() {
if (!peek()) {
return mEmpty; // copy
}
std::lock_guard<std::mutex> lock(mtx);
T& ret = mData[mReadP & mRoundRobinMask]; // get a ref
mReadP++;
return ret; // copy of ref
}
void push(const T& aItemRef) {
start:
if (!wait()) {
throw std::runtime_error("!Queue FULL!");
}
std::lock_guard<std::mutex> lock(mtx);
if(size() == mSize) {
goto start;
}
mData[mWriteP & mRoundRobinMask] = aItemRef;
mWriteP++;
}
bool peek() const {
return mWriteP != mReadP;
}
unsigned size() const {
return mWriteP > mReadP ? mWriteP - mReadP : mReadP - mWriteP; // mod (Read-Write)
}
bool wait() {
unsigned it = 0;
while (size() == mSize) {
if (it++ > 1000000) { return false; }
}
return true;
}
};
template<typename T>
const T Queue<T>::mEmpty = T{ };
int main(int, char**) {
using Method = std::function<void()>;
Queue<Method*> queue;
std::thread consumer([ & ] {
while (true) {
if (queue.peek()) {
auto task = queue.pop();
(*task)();
delete task;
}
}
});
std::thread producer1([ & ] {
unsigned index = 0;
while (true) {
auto id = index++;
auto task = new Method([ = ] {
std::cout << "Running task " << id << std::endl;
});
queue.push(task);
}
});
// std::thread producer2 ([ & ] {
// unsigned index = 0;
// while (true) {
// auto id = index++;
// auto task = new Method([ = ] {
// std::cout << "Running task " << id<< std::endl;
// });
// queue.push(task);
// }
// });
consumer.join();
producer1.join();
// producer2.join();
return 0;
}
#包括
#包括
#包括
#包括
样板
类队列{
私人:
static constexpr unsigned mSize=256;//仅限二次幂
静态constexpr unsigned mRoundRobinMask=mSize-1;
静态常数记忆;
T mData[mSize];
std::互斥mtx;
无符号mReadP=0;
无符号mWriteP=0;
公众:
常量T pop(){
如果(!peek()){
return mEmpty;//复制
}
标准:锁和防护锁(mtx);
T&ret=mData[mReadP&mRoundRobinMask];//获取引用
mReadP++;
return ret;//ref的副本
}
无效推送(常数T和aItemRef){
开始:
如果(!wait()){
抛出std::runtime_错误(“!队列已满!”);
}
标准:锁和防护锁(mtx);
如果(size()==mSize){
转到开始;
}
mData[mWriteP&mRoundRobinMask]=aItemRef;
mWriteP++;
}
bool peek()常数{
返回mWriteP!=mReadP;
}
无符号大小()常量{
返回mWriteP>mReadP?mWriteP-mReadP:mReadP-mWriteP;//mod(读写)
}
bool wait(){
无符号it=0;
while(size()==mSize){
如果(it++>1000000){返回false;}
}
返回true;
}
};
样板
const T Queue::mEmpty=T{};
int main(int,char**){
使用Method=std::function;
排队;
std::线程使用者([&]{
while(true){
if(queue.peek()){
自动任务=queue.pop();
(*任务)();
删除任务;
}
}
});
标准::螺纹生产商1([&]{
无符号索引=0;
while(true){
自动id=索引++;
自动任务=新方法([=]{
std::cout您的推送不是线程安全的
当两个线程在队列中只有一个可用插槽时调用时,两个线程都可以通过wait
,导致其中一个线程可能会覆盖队列中的现有元素。被覆盖的元素将不会被释放,从而导致内存泄漏
解决方案是在获得锁后再次检查队列是否已满。如果已满,则需要释放锁并再次等待,直到插槽可用
另一方面,wait
函数可以通过在while循环中包含一个sleep(0)
调用而变得更友好。这将减少等待时的功耗和CPU资源使用。您的示例存在许多问题
主要的问题是它不是线程安全的:push()
和pop()
修改非原子成员变量mReadP
和mWriteP
,而不受互斥锁的保护
第二个不太重要的问题是,等待要弹出的项目到达或释放要推送的空间通常是通过使用来完成的,这将挂起线程,直到达到条件为止
请尝试下面的版本,因为我用这些更改更新了它
我还添加了一个终止条件,以显示如何安全地取出所有线程,并减慢整个过程以显示发生了什么
#include <array>
#include <condition_variable>
#include <functional>
#include <iostream>
#include <mutex>
#include <thread>
#include <optional>
template<typename T>
class Queue {
private:
static constexpr unsigned mSize = 256; //power of two only
static constexpr unsigned mRoundRobinMask = mSize - 1;
std::array<T, mSize> mData;
std::mutex mtx;
unsigned mReadP = 0;
unsigned mWriteP = 0;
std::condition_variable notFull;
std::condition_variable notEmpty;
bool stopped = false;
public:
const std::optional<T> pop() {
// Always grab the mutex before accessing any shared members
std::unique_lock<std::mutex> lock(mtx);
// Wait until there is an item in the queue.
notEmpty.wait(lock, [&] {return stopped || mWriteP != mReadP; });
if(stopped)
return std::nullopt;
T& ret = mData[mReadP & mRoundRobinMask]; // get a ref
mReadP++;
// Wake any threads waiting on full buffer
notFull.notify_one();
return ret; // copy of ref
}
void push(const T& pItem) {
std::unique_lock<std::mutex> lock(mtx);
// Wait until there is space to put at least one item
notFull.wait(lock, [&] { return stopped || getCount() < mSize; });
if(stopped)
return;
mData[mWriteP & mRoundRobinMask] = pItem;
mWriteP++;
// Wake any threads waiting on empty buffer
notEmpty.notify_one();
}
unsigned getCount() const {
return mWriteP > mReadP ?
mWriteP - mReadP : mReadP - mWriteP; // mod (Read-Write)
}
void stop() {
// Signal the stop condition
stopped = true;
// Grabbing the lock before notifying is essential to make sure
// any worker threads waiting on the condition_variables.
std::unique_lock<std::mutex> lock(mtx);
// Wake all waiting threads
notFull.notify_all();
notEmpty.notify_all();
}
};
int main(int, char**) {
using Method = std::function<void()>;
Queue<Method> queue;
bool running = true;
std::thread consumer([ & ] {
while (running) {
auto task = queue.pop();
if(task) {
// If there was a task, execute it.
(*task)();
} else {
// No task means we are done.
return;
}
}
});
std::thread producer1([ & ] {
unsigned index = 0;
while (running) {
auto id = index++;
queue.push([ = ] {
std::cout << "Running task " << id << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
});
}
});
std::this_thread::sleep_for(std::chrono::seconds(2));
// Use pre-c++-20 mechanisms to signal the worker threads to stop their loops
running = false;
// If they're in the queue stop that too.
queue.stop();
consumer.join();
producer1.join();
return EXIT_SUCCESS;
}
#include具有更优雅的机制,如自动线程连接和条件变量::wait()
终止通过。有人被否决并投票关闭,因为他想要调试详细信息。你调试了吗?p.s.shared_ptr不是线程安全的,但那是另一个问题使用1个使用者运行时,1个生产者没有内存泄漏,可以正常运行数小时,不会中断。我如何调试?它只使用2个生产者就中断了s、 当我的内存用完时
#include <array>
#include <condition_variable>
#include <functional>
#include <iostream>
#include <mutex>
#include <thread>
#include <optional>
template<typename T>
class Queue {
private:
static constexpr unsigned mSize = 256; //power of two only
static constexpr unsigned mRoundRobinMask = mSize - 1;
std::array<T, mSize> mData;
std::mutex mtx;
unsigned mReadP = 0;
unsigned mWriteP = 0;
std::condition_variable notFull;
std::condition_variable notEmpty;
bool stopped = false;
public:
const std::optional<T> pop() {
// Always grab the mutex before accessing any shared members
std::unique_lock<std::mutex> lock(mtx);
// Wait until there is an item in the queue.
notEmpty.wait(lock, [&] {return stopped || mWriteP != mReadP; });
if(stopped)
return std::nullopt;
T& ret = mData[mReadP & mRoundRobinMask]; // get a ref
mReadP++;
// Wake any threads waiting on full buffer
notFull.notify_one();
return ret; // copy of ref
}
void push(const T& pItem) {
std::unique_lock<std::mutex> lock(mtx);
// Wait until there is space to put at least one item
notFull.wait(lock, [&] { return stopped || getCount() < mSize; });
if(stopped)
return;
mData[mWriteP & mRoundRobinMask] = pItem;
mWriteP++;
// Wake any threads waiting on empty buffer
notEmpty.notify_one();
}
unsigned getCount() const {
return mWriteP > mReadP ?
mWriteP - mReadP : mReadP - mWriteP; // mod (Read-Write)
}
void stop() {
// Signal the stop condition
stopped = true;
// Grabbing the lock before notifying is essential to make sure
// any worker threads waiting on the condition_variables.
std::unique_lock<std::mutex> lock(mtx);
// Wake all waiting threads
notFull.notify_all();
notEmpty.notify_all();
}
};
int main(int, char**) {
using Method = std::function<void()>;
Queue<Method> queue;
bool running = true;
std::thread consumer([ & ] {
while (running) {
auto task = queue.pop();
if(task) {
// If there was a task, execute it.
(*task)();
} else {
// No task means we are done.
return;
}
}
});
std::thread producer1([ & ] {
unsigned index = 0;
while (running) {
auto id = index++;
queue.push([ = ] {
std::cout << "Running task " << id << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
});
}
});
std::this_thread::sleep_for(std::chrono::seconds(2));
// Use pre-c++-20 mechanisms to signal the worker threads to stop their loops
running = false;
// If they're in the queue stop that too.
queue.stop();
consumer.join();
producer1.join();
return EXIT_SUCCESS;
}