【问题标题】:Smart pointers Object pool test application fails at finish/exit智能指针对象池测试应用程序在完成/退出时失败
【发布时间】:2019-06-20 16:07:06
【问题描述】:

我在 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;
}

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

嗯,这个想法是应用程序运行(显然)正常。
但在它完成后,我立即出现此错误:

有谁知道我该如何解决这个问题?

【问题讨论】:

  • 当你点击“重试”调试程序时,调试器说什么/哪里有问题?
  • 我现在手头没有它(现在我在家,我有源代码但没有编译器)。但是 IIRC,它说类似“未处理的异常 win32 和 4 位数字”......
  • @scuberula:我在 Ubuntu 上试过,程序运行了好几次都没有问题。但如果使用线程,这并不意味着什么。

标签: c++ multithreading qt smart-pointers objectpool


【解决方案1】:

这个错误的原因与智能指针有关,而不是多线程。

您使用自定义删除器将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 将尝试销毁其所有成员。对于每个成员,自定义删除器。自定义删除器调用addadd 将指针压入堆栈。

从逻辑上讲,这是一个无限循环。但它更有可能通过破坏数据结构的一致性来创建某种未定义的行为。 (即堆栈在被破坏时不应推送新成员)。当没有足够的内存添加到堆栈数据结构时,由于函数堆栈溢出或文字堆栈溢出 (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&lt;bool&gt; 并在进入临界区之前执行读取检查(如果为 false,则可以跳过临界区)但这需要 1)您再次检查临界区中的值,然后 2 ) 你从 true 变为 false 恰好一次。

【讨论】:

  • 感谢您抽出宝贵时间回答。是的,它有效:) 干杯!对不起,我没有投票权...
  • 没关系,因为问题已被接受 :) 很高兴它有效,尽管您可能会彻底测试它
猜你喜欢
  • 2021-12-07
  • 1970-01-01
  • 2015-03-05
  • 2012-09-10
  • 2021-02-19
  • 2020-03-05
  • 1970-01-01
  • 1970-01-01
  • 2013-03-16
相关资源
最近更新 更多