智能指针对象池测试应用程序在 finish/exit 失败
Smart pointers Object pool test application fails at finish/exit
我在 Qt/C++11 中编写了一个多线程应用程序,Windows.
这个想法是使用智能指针从池中回收一些字符串。
这是 stringpool.cpp:
#include "stringpool.h"
QMutex StringPool::m_mutex;
int StringPool::m_counter;
std::stack<StringPool::pointer_type<QString>> StringPool::m_pool;
StringPool::pointer_type<QString> StringPool::getString()
{
QMutexLocker lock(&m_mutex);
if (m_pool.empty())
{
add();
}
auto inst = std::move(m_pool.top());
m_pool.pop();
return inst;
}
void StringPool::add(bool useLock, QString * ptr)
{
if(useLock)
m_mutex.lock();
if (ptr == nullptr)
{
ptr = new QString();
ptr->append(QString("pomo_hacs_%1").arg(++m_counter));
}
StringPool::pointer_type<QString> inst(ptr, [this](QString * ptr) { add(true, ptr); });
m_pool.push(std::move(inst));
if(useLock)
m_mutex.unlock();
}
这里是 stringpool.h:
#pragma once
#include <QMutex>
#include <QString>
#include <functional>
#include <memory>
#include <stack>
class StringPool
{
public:
template <typename T> using pointer_type = std::unique_ptr<T, std::function<void(T*)>>;
//
StringPool() = default;
pointer_type<QString> getString();
private:
void add(bool useLock = false, QString * ptr = nullptr);
//
static QMutex m_mutex;
static int m_counter;
static std::stack<pointer_type<QString>> m_pool;
};
这是测试应用程序:
#include <QtCore>
#include "stringpool.h"
static StringPool Pool;
class Tester : public QThread
{
public:
void run() override
{
for(int i = 0; i < 20; i++)
{
{
auto str = Pool.getString();
fprintf(stderr, "Thread %p : %s \n", QThread::currentThreadId(), str->toUtf8().data());
msleep(rand() % 500);
}
}
fprintf(stderr, "Thread %p : FINITA! \n", QThread::currentThreadId());
}
};
#define MAX_TASKS_NBR 3
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
Tester tester[MAX_TASKS_NBR];
for(auto i = 0; i < MAX_TASKS_NBR; i++)
tester[i].start();
for(auto i = 0; i < MAX_TASKS_NBR; i++)
tester[i].wait();
//
return 0;
}
编译正常,运行并产生以下结果:
嗯,这个想法是应用程序运行(显然)正常。
但是在它完成后立即出现此错误:
有谁知道我该如何解决这个问题?
此错误的原因与智能指针有关,与多线程无关。
您使用自定义删除器将 pointer_type
定义为 unique_ptr
的别名
template <typename T> using pointer_type = std::unique_ptr<T, std::function<void(T*)>>;
您使用自定义删除器创建字符串
void StringPool::add(bool useLock, QString * ptr)
{
if (ptr == nullptr)
{
ptr = new QString();
ptr->append(QString("pomo_hacs_%1").arg(++m_counter));
}
StringPool::pointer_type<QString> inst(ptr, [this](QString * ptr) { add(true, ptr); }); // here
m_pool.push(std::move(inst));
}
程序结束时,m_pool
超出范围并运行其析构函数。
考虑执行路径...m_pool
将尝试销毁其所有成员。对于每个成员,自定义删除器。自定义删除器调用 add
。 add
将指针压入堆栈。
逻辑上这是一个无限循环。但它更有可能通过破坏数据结构的一致性来创建某种未定义的行为。 (即堆栈在被破坏时不应推送新成员)。当没有足够的内存添加到堆栈数据结构时,可能会由于函数堆栈溢出或文字堆栈溢出(heh)而发生异常。由于异常发生在未处理的析构函数中,它会立即结束程序。但它也很可能是由于破坏时的推动而导致的段错误。
修复:
我已经不喜欢你的 add
功能了。
StringPool::pointer_type<QString> StringPool::getString()
{
QMutexLocker lock(&m_mutex);
if (m_pool.empty())
{
auto ptr = new QString(QString("pomo_hacs_%1").arg(++m_counter));
return pointer_type<QString>(ptr, [this](QString* ptr) { reclaim(ptr); });
}
auto inst = std::move(m_pool.top());
m_pool.pop();
return inst;
}
void StringPool::reclaim(QString* ptr)
{
QMutexLocker lock(&m_mutex);
if (m_teardown)
delete ptr;
else
m_pool.emplace(ptr, [this](QString* ptr) { reclaim(ptr); });
}
StringPool::~StringPool()
{
QMutexLocker lock(&m_mutex);
m_teardown = true;
}
StringPool
是静态的 class 但通过此修复,它现在必须是单例 class.
将 m_teardown
拉出临界区可能很诱人,但它是共享数据,因此这样做将为竞争条件打开大门。作为过早的优化,您可以使 m_teardown
成为 std::atomic<bool>
并在进入临界区之前执行读取检查(如果为 false 则可以跳过临界区)但这需要 1) 您再次检查值关键部分和 2) 你从 true 变为 false 恰好一次。
我在 Qt/C++11 中编写了一个多线程应用程序,Windows.
这个想法是使用智能指针从池中回收一些字符串。
这是 stringpool.cpp:
#include "stringpool.h"
QMutex StringPool::m_mutex;
int StringPool::m_counter;
std::stack<StringPool::pointer_type<QString>> StringPool::m_pool;
StringPool::pointer_type<QString> StringPool::getString()
{
QMutexLocker lock(&m_mutex);
if (m_pool.empty())
{
add();
}
auto inst = std::move(m_pool.top());
m_pool.pop();
return inst;
}
void StringPool::add(bool useLock, QString * ptr)
{
if(useLock)
m_mutex.lock();
if (ptr == nullptr)
{
ptr = new QString();
ptr->append(QString("pomo_hacs_%1").arg(++m_counter));
}
StringPool::pointer_type<QString> inst(ptr, [this](QString * ptr) { add(true, ptr); });
m_pool.push(std::move(inst));
if(useLock)
m_mutex.unlock();
}
这里是 stringpool.h:
#pragma once
#include <QMutex>
#include <QString>
#include <functional>
#include <memory>
#include <stack>
class StringPool
{
public:
template <typename T> using pointer_type = std::unique_ptr<T, std::function<void(T*)>>;
//
StringPool() = default;
pointer_type<QString> getString();
private:
void add(bool useLock = false, QString * ptr = nullptr);
//
static QMutex m_mutex;
static int m_counter;
static std::stack<pointer_type<QString>> m_pool;
};
这是测试应用程序:
#include <QtCore>
#include "stringpool.h"
static StringPool Pool;
class Tester : public QThread
{
public:
void run() override
{
for(int i = 0; i < 20; i++)
{
{
auto str = Pool.getString();
fprintf(stderr, "Thread %p : %s \n", QThread::currentThreadId(), str->toUtf8().data());
msleep(rand() % 500);
}
}
fprintf(stderr, "Thread %p : FINITA! \n", QThread::currentThreadId());
}
};
#define MAX_TASKS_NBR 3
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
Tester tester[MAX_TASKS_NBR];
for(auto i = 0; i < MAX_TASKS_NBR; i++)
tester[i].start();
for(auto i = 0; i < MAX_TASKS_NBR; i++)
tester[i].wait();
//
return 0;
}
编译正常,运行并产生以下结果:
嗯,这个想法是应用程序运行(显然)正常。
但是在它完成后立即出现此错误:
有谁知道我该如何解决这个问题?
此错误的原因与智能指针有关,与多线程无关。
您使用自定义删除器将 pointer_type
定义为 unique_ptr
的别名
template <typename T> using pointer_type = std::unique_ptr<T, std::function<void(T*)>>;
您使用自定义删除器创建字符串
void StringPool::add(bool useLock, QString * ptr)
{
if (ptr == nullptr)
{
ptr = new QString();
ptr->append(QString("pomo_hacs_%1").arg(++m_counter));
}
StringPool::pointer_type<QString> inst(ptr, [this](QString * ptr) { add(true, ptr); }); // here
m_pool.push(std::move(inst));
}
程序结束时,m_pool
超出范围并运行其析构函数。
考虑执行路径...m_pool
将尝试销毁其所有成员。对于每个成员,自定义删除器。自定义删除器调用 add
。 add
将指针压入堆栈。
逻辑上这是一个无限循环。但它更有可能通过破坏数据结构的一致性来创建某种未定义的行为。 (即堆栈在被破坏时不应推送新成员)。当没有足够的内存添加到堆栈数据结构时,可能会由于函数堆栈溢出或文字堆栈溢出(heh)而发生异常。由于异常发生在未处理的析构函数中,它会立即结束程序。但它也很可能是由于破坏时的推动而导致的段错误。
修复:
我已经不喜欢你的 add
功能了。
StringPool::pointer_type<QString> StringPool::getString()
{
QMutexLocker lock(&m_mutex);
if (m_pool.empty())
{
auto ptr = new QString(QString("pomo_hacs_%1").arg(++m_counter));
return pointer_type<QString>(ptr, [this](QString* ptr) { reclaim(ptr); });
}
auto inst = std::move(m_pool.top());
m_pool.pop();
return inst;
}
void StringPool::reclaim(QString* ptr)
{
QMutexLocker lock(&m_mutex);
if (m_teardown)
delete ptr;
else
m_pool.emplace(ptr, [this](QString* ptr) { reclaim(ptr); });
}
StringPool::~StringPool()
{
QMutexLocker lock(&m_mutex);
m_teardown = true;
}
StringPool
是静态的 class 但通过此修复,它现在必须是单例 class.
将 m_teardown
拉出临界区可能很诱人,但它是共享数据,因此这样做将为竞争条件打开大门。作为过早的优化,您可以使 m_teardown
成为 std::atomic<bool>
并在进入临界区之前执行读取检查(如果为 false 则可以跳过临界区)但这需要 1) 您再次检查值关键部分和 2) 你从 true 变为 false 恰好一次。