Причина этой ошибки связана с умным указателем, а не с многопоточностью.
Вы определяете 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
толкает указатель на стек.
Логически это бесконечный цикл. Но более вероятно создать какое-то неопределенное поведение, нарушив согласованность структуры данных. (т.е. стек не должен выдвигать новых членов, пока он разрушается). Исключение может произойти из-за переполнения стека функций или буквального переполнения стека (хех), когда недостаточно памяти для добавления в структуру данных стека. Поскольку исключение возникает в деструкторе необработанным, оно немедленно завершает программу. Но это также может быть ошибкой сегмента из-за толчка при разрушении.
Исправления:
Мне уже не понравилась ваша 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 ) вы переходите от истинного к ложному ровно один раз.