Консольное приложение multi-thead Qt зависает при выходе (используя ctrl-c) - PullRequest
1 голос
/ 01 апреля 2019

Я пишу простое консольное приложение, которое порождает некоторых работников, используя QThreadPool. Я компилирую на Windows 10, используя Qt 5.12.2 и Microsoft Visual C ++ Compiler 14.0 (amd64). Работа должна начинаться через равные промежутки времени с использованием таймера, но работа занимает больше времени, чем интервал. 8 потоков должны быть в состоянии справиться с рабочей нагрузкой. Я запускаю приложение, и оно работает очень гладко и, как и ожидалось.

Когда я хочу выйти из приложения командной строки, я нажимаю Ctrl-C для остановки. Затем приложение зависает. Таймер останавливается, и весь вывод останавливается, но он не возвращает меня в командную строку. Я должен открыть диспетчер задач, чтобы выйти из приложения. Я уверен, что это как-то связано с неправильной очисткой QThreadPool, но я не могу найти, как его очистить. Спасибо за предложения.

Я попытался перехватить сигнал уничтожения из таймера и сигнал aboutToQuit из приложения. Ни одного пожара. Если вы закомментируете начало пула потоков, приложение закроется правильно. Я также создал пул потоков как переменную-член в классе MyTimer и преобразовал рабочий в указатель. Все изменения привели к одному и тому же результату, зависание при выходе.

У меня нет отладчика на этом компьютере для подключения и просмотра места зависания приложения.

mytimer.h

#ifndef MYTIMER_H
#define MYTIMER_H

#include <QDebug>
#include <QTimer>
#include <QThreadPool>

class MyWorker : public QRunnable
{
public:
    void run()
    {
        QThread::msleep(150);  //simulate some work
        qDebug() << ".";
    }
};

class MyTimer : public QObject
{
    Q_OBJECT
public:
    MyTimer()
    {
        QThreadPool::globalInstance()->setMaxThreadCount(8);
        worker.setAutoDelete(false);
        // setup signal and slot
        connect(&timer, SIGNAL(timeout()), this, SLOT(MyTimerSlot()));

        timer.setTimerType(Qt::PreciseTimer);
        // msec
        timer.start(50);
    }

    QTimer timer;
    MyWorker worker;

public slots:
    void MyTimerSlot()
    {
        //Comment the below line and the ctrl-c will work.
        QThreadPool::globalInstance()->start(&worker);
        qDebug() << "-";
    }
};


#endif // MYTIMER_H

main.cpp

#include <QCoreApplication>
#include "mytimer.h"


int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    MyTimer timer;
    return a.exec();
}

Я ожидаю, что когда я нажму ctrl-c во время выполнения приложения, приложение выйдет корректно и вернет управление в командную строку.

1 Ответ

0 голосов
/ 02 апреля 2019

У вас есть две проблемы:

  1. Когда приложение убивается с помощью Ctrl-C, оно получает сигнал Kill или Abort от Os и завершается как можно скорее.Поэтому никакие деструкторы или сигналы aboutToQuit не будут запущены вследствие прерывания обычного разматывания стека.

  2. Ваш рабочий класс не обрабатывает прерывания.Обычно у функции запуска есть какой-то цикл, который повторяет работу с некоторым фрагментом данных.Чтобы корректно остановить QRunnable, вы должны выйти из функции запуска.Вы можете сделать это безопасно, используя переменную-член std :: atomic boolean, чтобы разорвать цикл на класс MyWorker, и использовать signal / slots для его переключения.

Вот исправление для проблемы 1:

#include <QCoreApplication>
#include <csignal>
#include "mytimer.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    MyTimer timer;

    QObject::connect(qApp, &QCoreApplication::aboutToQuit, &timer, &MyTimer::workerStopRequested);

    signal(SIGTERM, [](int sig) { qApp->quit(); });
    signal(SIGABRT, [](int sig) { qApp->quit(); });
    signal(SIGINT, [](int sig) { qApp->quit(); });
    signal(SIGKILL, [](int sig){ qApp->quit(); });

    return a.exec();
}

Используя утилиты, предоставленные в заголовочном файле csignal, вы можете поймать сигнал убийства и заставить приложение выйти, вызывая сигнал aboutToQuit.

Этот сигнал также используется для указания экземпляру MyTimer прекратить работу своих сотрудников, запускающих сигнал workerStopRequested, который является частью решения проблемы 2:

#ifndef MYTIMER_H
#define MYTIMER_H

#include <QDebug>
#include <QTimer>
#include <QThreadPool>

class MyWorker : public QObject, public QRunnable
{
    Q_OBJECT
public:
    explicit MyWorker(QObject* parent = nullptr) :
        QObject(parent),
        QRunnable()
    {
        aborted = false;
    }

    void run()
    {
        while (!aborted)
        {
            QThread::msleep(150);  //simulate some work
            qDebug() << ".";
        }
    }

public slots:
    void abort()
    {
        aborted = true;
        qDebug() << "stopped.";
    }

protected:
    std::atomic<bool> aborted;
};

class MyTimer : public QObject
{
    Q_OBJECT
public:
    MyTimer()
    {
        QThreadPool::globalInstance()->setMaxThreadCount(8);
        worker.setAutoDelete(false); // <-- Good. Worker is not a simple QRunnable anymore

        // setup signal and slot
        connect(&timer, SIGNAL(timeout()), this, SLOT(MyTimerSlot()));

        connect(this, &MyTimer::workerStopRequested, &worker, &MyWorker::abort);

        timer.setTimerType(Qt::PreciseTimer);
        // msec
        timer.start(50);

    }

    QTimer timer;
    MyWorker worker;

signals:
    void workerStopRequested();

public slots:
    void MyTimerSlot()
    {
        //Comment the below line and the ctrl-c will work.
        QThreadPool::globalInstance()->start(&worker);
        qDebug() << "-";
    }
};

#endif // MYTIMER_H

Класс MyWorker наследуется от QObject, чтобы использовать сигнал / слоты для выхода из цикла обработки функции запуска.Логическое «aborted» - это атомарная переменная, гарантирующая, что к ней доступен потокобезопасный.(Atomics - это функция c ++ 11)

Устанавливается в false после того, как сигнал workerStopRequested (см. Main.cpp) и слот abort () выполнен, как следствие соединения, выполненного вконструктор класса MyTimer.

Обратите внимание, что при прерывании прерывания цикл обработки прекратится на следующей итерации.Это означает, что вы можете увидеть не более 8 точек, напечатанных на экране после «остановленной» строки (по одной для каждой нити в соответствии с максимальным количеством нитей).

...