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