Подтвердить что ты не робот

Как правильно прерывать бесконечный цикл QThread

Я закончил реализацию программы Go, где человек может прерывать в любое время программное обеспечение, чтобы заказать его для воспроизведения. В принципе, у меня есть алгоритм, работающий в другом потоке, который имеет в любой момент доступный "лучший ход", который он продолжает улучшать.

Мой вопрос: как правильно прервать поток в бесконечном цикле?

Я пробовал несколько вещей и решил сделать это:

class MyWorker : public QObject
{
    Q_OBJECT
public:
    MyWorker();
    ~MyWorker();

public:
    ThreadControl * getThreadControl();

public slots:
    void work();

private:
    void endOfComputation();

private:
    ThreadControl * threadControl;
}

Обратите внимание, что я не подклассы QThread:

class ThreadControl : public QObject
{
    Q_OBJECT
public:
    ThreadControl();

public:
    bool getAbort();
    Parameter getParameter();
    void setParameter(Parameter & param);

public slots:
    void setAbort(bool b);

private:
    QMutex mutex;
    bool abort;
    Parameter param;
};

И, наконец, бесконечный цикл кодируется следующим образом:

void Myworker::work()
{
    // ...
    forever
    {
        abort = threadControl->getAbort();
        if(abort)
        {
            break;
        }
        // ...
    }
    endOfComputation();
}

Затем, как вы можете догадаться, в main я регулярно вызываю ThreadControl::setAbort(true)

В основном, я просто сохраняю указатель на логическое в основном потоке, и я переключаю его, когда хочу. (Булево инкапсулировано в ThreadControl, поэтому я могу правильно заблокировать его с помощью мьютекса). Пока все хорошо, это сработало для меня... Но мне это кажется отвратительным! Переключение указателей на boolean звучит как... плохое программирование для меня...

Проблема заключается в том, что документация в Интернете в основном (полностью?) о потоках производителей и потребителей, которые заканчиваются через некоторое время, а не когда их спрашивают. Прерывание потока не развивается, поэтому я спрашиваю: Есть ли лучший способ?

4b9b3361

Ответ 1

Это хороший способ сделать это. Вы также можете посмотреть, как это было сделано увеличить поток. Поскольку он использует исключения для прерывания потока, он позволяет вам прерывать поток в нескольких местах, используя interruption_point. Используя свою модель потоков, вы можете написать свою функцию потока следующим образом:

void myFunction(){
    boost::this_thread::interruption_point();
}

void Myworker::work()
{
    // ...
    try 
    {
        forever
        {
            boost::this_thread::interruption_point();
            // do some work
            boost::this_thread::interruption_point();
            // work again
            myFunction(); // interruption might be triggered inside this function
        }
        endOfComputation();
    }
    catch(boost::thread_interrupted const &){
         // The thread has been interrupted
    }
}

Я думаю, что внутренне они используют логическое значение (для потока), которое установлено в true при вызове метода boost::thread::interrupt().

ИЗМЕНИТЬ

Моя цель - показать вам, как эта проблема решила эту проблему. Конечно, это не будет работать с вашим QThread. Я не хочу, чтобы вы переключались на boost:: thread.

EDIT2 Быстрая реализация с QThread:

void function();

class MyWorker : public QThread {
public:

    MyWorker() : m_isInterrupted(false) {}

    class InterruptionException {
    public:
        InterruptionException(){}
    };
    static void interruptionPoint(){
        MyWorker * myWorker = dynamic_cast<MyWorker*>(QThread::currentThread());
        if(myWorker){
            if(myWorker->m_isInterrupted){
                throw InterruptionException();
            }
        }
    }

public slots:
    void interrupt(){
        m_isInterrupted = true;
    }
    void work(){
        try {
            while(true){
                MyWorker::interruptionPoint();
                function();
            }
        }
        catch(InterruptionException const &){

        }
    }

private:
    bool m_isInterrupted;
};

void function(){
    MyWorker::interruptionPoint();
}

Ответ 2

Я понимаю, что переключение флагов вместо вызова каких-то специализированных методов выглядит уродливо, но в реальной жизни это лучший способ сделать это. Специализированные методы обычно очень опасны, например, QThread:: terminate(). В некоторых средах предусмотрены готовые к использованию флаги, поэтому вам не нужно добавлять свои собственные логики, например Java с помощью Thread.interrupt() и Thread.interrupted(). Qt не имеет такой вещи, и, может быть, это тоже хорошо, потому что прерывание иногда работает на контр-интуитивно в Java. Возьмите разницу между Thread.interrupted() и Thread.isInterrupted(), например. Это абсолютно противоречиво. Если вы не обратитесь к документам, вы вряд ли сможете догадаться, в чем разница. Хуже того, поскольку один из них является статичным, вы можете подумать, что это разница, но это не так. Кроме того, операции ввода-вывода старого стиля не могут быть прерваны на Java, но NIO в новом стиле может и не иметь смысла.

Иногда вы можете избежать флагов, используя что-то другое. Например, если поток имеет какую-то входную очередь элементов для обработки, вы можете использовать специальный элемент конца очереди, чтобы указать, что он должен останавливаться прямо там. Но иногда нет удобного места для этого, что при использовании булевых флагов.

Единственное, что вы могли бы сделать, чтобы немного оптимизировать свой код, - это заменить блокировку мьютекса летучим bool. Однако это не гарантирует упорядочение доступа к памяти, поэтому, если ваш поток зависит от того, что происходит во время волатильной записи, вы не должны идти этим путем. Или вы можете использовать QAtomicInt вместо своих барьеров памяти. Но если нет значительного влияния на производительность, использование мьютекса прекрасное и безопасное тоже.

Я также заменил бы цикл:

while (!threadControl->getAbort()) {
  // ...