0

我在 Windows 的 Qt/C++11 中编写了一个多线程应用程序。
这个想法是使用智能指针从池中获取和回收一些字符串。
这是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;
}

它编译好,它运行并产生以下结果:

程序输出

好吧,这个想法是应用程序运行(显然)OK。
但它完成后立即出现此错误:
带有 abort 的弹出窗口被称为 message
有谁知道我该如何解决这个问题?

4

1 回答 1

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是一个静态类,但有了这个修复它现在必须是一个单例类。

退出关键部分可能很诱人m_teardown,但它是共享数据,因此这样做将为竞争条件打开大门。作为过早的优化,您可以在进入临界区之前m_teardown进行std::atomic<bool>读取检查(如果为 false,则可以跳过临界区),但这需要 1)您在临界区再次检查该值,以及 2)您从 true 更改为只假一次。

于 2019-06-22T08:51:32.780 回答