Синхронизация событий между двумя потоками в Qt

У меня есть две темы, скажем, нить "A" и нить "B". Поток "A" отправляет пользовательское QEvent в поток "B", и затем он должен ждать, пока поток "B" не обработает это событие.

Что я сделал до сих пор:

Мой класс событий:

class IPCMessageEvent : public QEvent
{
public:
   IPCMessageEvent(QWaitCondition* pConditions) : QEvent(IPC_MESSAGE_RECEIVED)
                                                , mpWaitCondition(pConditions)
   { };
   ~IPCMessageEvent()
   {
      mpWaitCondition->wakeOne();
   };
private:
   QWaitCondition* mpWaitCondition;
};

Моя тема "А":

QWaitCondition recvCondition;
IPCMessageEvent* pEvent = new IPCMessageEvent(&recvCondition);

QCoreApplication::postEvent(gpApp, pEvent);

QMutex  mutex; 
        mutex.lock();

recvCondition.wait(&mutex, IPC_MESSAGE_WAIT_TIMEOUT);

Моя ветка "B": обрабатывает полученное событие и уничтожает его. Деструктор ~IPCMessageEvent вызывается и поэтому wakeOne() будет инициировано для recvCondition в теме "А".

Кажется, все работает отлично, это только одно! Похоже, иногда ~IPCMessageEvent вызывается раньше, чем ожидалось...

QCoreApplication::postEvent(gpApp, pEvent);

<---- pEvent is already destroyed here ---->

QMutex mutex; 
       mutex.lock();

Так что мой recvCondition.wait(&mutex, IPC_MESSAGE_WAIT_TIMEOUT); будет заблокирован и достигнет таймаута.

Существуют ли другие способы синхронизации? Или, может быть, у кого-то есть предложения, как исправить / преодолеть эту проблему?

2 ответа

Решение

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

Поэтому вам нужно заблокировать мьютекс перед публикацией события. Однако для этого требуется, чтобы ваш поток B также блокировал этот мьютекс при обработке события. В противном случае вы не сможете предотвратить состояние гонки, поскольку у потока B нет причин ждать чего-либо (или знать, что он должен "ждать", пока поток A не будет готов в ожидании переменной условия).

Альтернатива:

Если вы используете соединение сигнал / слот между двумя потоками (или объектами, живущими в двух потоках), вы можете использовать Qt::BlockingQueuedConnection, Это гарантирует, что поток A блокируется после испускания сигнала до тех пор, пока цикл обработки событий в потоке B не обработает его.

Спасибо Йоханнесу, мне действительно нужно попробовать и использовать предложенную вами альтернативу с сигналами / слотами.

Сейчас я сделал следующее: я создал QMutex и логический флаг, которые используются между потоком "A" и потоком "B".

bool    mIsProcessingMessage;
QMutex  mIsProcessingMessageLock;

В теме "А" я публикую свое событие так:

IPCMessageEvent* pEvent = new IPCMessageEvent();
{   // Inform everyone that we will be processing our message.
    QMutexLocker locker(&mIsProcessingMessageLock);
    mIsProcessingMessage = true;
};

QCoreApplication::postEvent(gpApp, pEvent, Qt::HighEventPriority);

forever  // Loop until event will get processed.
{
    QMutexLocker locker(&mIsProcessingMessageLock);

    if (mIsProcessingMessage == false)
       break;

    ::Sleep(2);  // Don't load up the CPU.
};

В потоке "B", когда мое событие обрабатывается, я просто устанавливаю свой флаг "mIsProcessingMessage" в true так:

{
   QMutexLocker locker(&mIsProcessingMessageLock);
   mIsProcessingMessage = false;
};

Может быть, это не лучшее решение, но пока оно работает;)

Другие вопросы по тегам