2017-02-09 2 views
1

У меня есть два класса: обработчик и рабочий, связанный с сигналом и слотами. Вот это упрощенная версия (псевдо-код):Слоты внутри QThread, работающие одновременно

Обработчик Конструктор:

Handler::Handler(QObject *parent) : QObject(parent) 
{ 
    m_workerThread = new QThread; 
    m_worker = new Worker; 
    m_worker->moveToThread(m_workerThread); 
    m_workerThread->start(); 

    connect(this, &Handler::doWork, m_worker, &Worker::doWork); 
    connect(this, &Handler::stopWork, m_worker, &Worker::stopWork); 

    emit doWork(); 
} 

работник

void Worker::doWork() 
{ 
    qDebug()<<"------" 
    qDebug()<<"Start do work executing in: "<<QThread::currentThreadId(); 
    //doing some work 
    m_timer->start();//a singleshot timer that calls doWork() on timeout 
    qDebug()<<"work done"; 
    qDebug()<<"------" 

} 

void Worker::stopWork() 
{ 
    qDebug()<<"Stop timer executing in: "<<QThread::currentThreadId(); 
    m_timer->stop(); 
} 

Поэтому в основном работают начинается после того, как излучающие "DoWork" из обработчика. Слот «doWork» имеет таймер однократного таймера, который вызывает одну и ту же функцию снова через определенное время. Спустя некоторое время обработчик излучает сигнал «stopWork». Вот мой отладочный вывод:

------ 
Start do work executing in: 0x65602450 
work done 
------ 
------ 
Start do work executing in: 0x65602450 
work done 
------ 
------ 
Start do work executing in: 0x65602450 
work done 
------ 
------ 
Start do work executing in: 0x65602450 
stop work emitted from handler in: 0x750a7000 
Stop timer executing in: 0x65602450 
work done 
------ 
------ 
Start do work executing in: 0x65602450 
work done 
------ 
etc... 

Так что я не понимаю, как это вообще возможно, что мой рабочий поток выполняет два слота (DoWork и stopWork) в то же время? Должен ли сигнал stopWork быть отправлен и ждать, пока поток не станет Idle перед выполнением слота «stopWork»?

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

Также из моих тестов я понял, что это происходит в 30-40% случаев.

+1

Что происходит в разделе «Выполнение какой-либо работы» в «Worker :: doWork»? Если в любой момент во время этого кода управление возвращается (хотя и ненадолго) в цикл событий, то я думаю, что вполне возможно, что другой запрошенный сигнал начнет обрабатываться. Следовательно, ваши слоты не выполняются параллельно, они фактически вложены. –

+0

Я вызываю некоторые функции, реализованные другими классами. Не могли бы вы привести несколько примеров того, как управление возвращается в цикл событий? Я не могу понять, так ли это в тех функциях, которые я звоню! – luffy

+0

Большое спасибо за подсказку. Ты был прав. Я узнал, что код, который я вызывал, использует «QCoreApplication :: processEvents()» – luffy

ответ

0

Использование технологий синхронизации потоков, таких как QMutex и/или QSemaphore, может решить вашу проблему.

Например, если вы создали QMutex в классе Handler и передал его в рабочий поток:

Handler::Handler(QObject *parent) : QObject(parent) 
{ 
    //Class variable : QMutex* mutex 
    mutex = new QMutex(); 
    m_workerThread = new QThread; 
    m_worker = new Worker; 
    m_worker->moveToThread(m_workerThread); 
    m_workerThread->start(); 

    connect(this, &Handler::doWork, m_worker, &Worker::doWork); 
    connect(this, &Handler::stopWork, m_worker, &Worker::stopWork,Qt::BlockingQueuedConnection); 

    emit doWork(mutex); 
} 

, а затем называется механизм блокировки этой общей QMutex от внутри другого потока:

void Worker::doWork(QMutex* mutex) 
{ 
    mutex->lock(); 
    qDebug()<<"------" 
    qDebug()<<"Start do work executing in: "<<QThread::currentThreadId(); 
    //doing some work 
    m_timer->start();//a singleshot timer that calls doWork() on timeout 
    qDebug()<<"work done"; 
    qDebug()<<"------" 
    mutex->unlock(); 

} 

И использовали ту же логику в классе Handler, как раз перед тем, как испустить «остановить» сигнал:

... 
mutex->lock(); 
emit stopWork(); 
mutex->unlock(); 
... 

Таким образом, вы могли бы избежать нежелательного параллелизма.

BlockingQueuedConnection flag здесь гарантирует, что ваш основной поток не будет продвигаться без остановки, завершен, поэтому не отпирает мьютекс до того, как задание будет выполнено.

Это логика, которую я пытаюсь использовать при решении ситуаций параллелизма и гонок данных, надеюсь, вам это тоже поможет.

+0

, проблема в том, что это, вероятно, вызовет тупик, потому что я буду пытаться заблокировать Mutex дважды из того же потока – luffy

+0

Извините, мой плохой. Я не знал, что вы активируете один и тот же слот с помощью таймера одиночного снимка. Но это не меняет основного факта, ваша очевидная необходимость синхронизации потоков. В этом случае вы можете добавить строку 'mutex-> unlock()' в слот с тайм-аутом, прежде чем снова вызвать свой текущий слот Worker :: doWork'. Таким образом, в рабочем потоке можно избежать тупика. –

Смежные вопросы