Как правильно прервать бесконечный цикл QThread - PullRequest
12 голосов
/ 04 февраля 2011

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

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

Ответы [ 2 ]

7 голосов
/ 04 февраля 2011

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

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

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

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

while (!threadControl->getAbort()) {
  // ...
5 голосов
/ 04 февраля 2011

Это звучит как хороший способ сделать это.Вы также можете посмотреть, как это было сделано Boost Thread .Поскольку он использует исключения для прерывания потока, он позволяет вам прерывать поток в нескольких местах, используя 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().*

РЕДАКТИРОВАТЬ

Моя цель - показать вам, как Boost решил эту проблему.Конечно, это не будет работать с вашим 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();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...