0

I am trying to pause my thread waiting for an user action. I know I could use Qt::BlockingQueuedConnection but that is not the point here. I would like to use QWaitCondition but I don't understand in this particular case why I need a QMutex. Consider this code :

class MyWorker: public QThread
{
    private:
        QMutex mDummy;
        QWaitCondition mStep1;
        void doStuff1(){}
        void doStuff2(){}

    signals:
        void step1Finished();

    public:
        MyWorker(...): {}

    protected:
        void run()
        {
            doStuff1();
            emit step1Finished();

            mDummy.lock();
            mStep1.wait(mDummy);
            mDummy.unlock();

            doStuff2();
        }
}

In this case the QMutex mDummy seems useless to me. I use it only because wait() need it as parameter. I know that wait() unlock the mutex then (re)lock it after waking up, but why there no possibility to use wait() without it?

NiHoT
  • 342
  • 5
  • 17
  • 1
    Inasmuch as QWaitCondition is Qt's version of a condition variable, this question might be informative: https://stackoverflow.com/questions/2763714/why-do-pthreads-condition-variable-functions-require-a-mutex – Jeremy Friesner Aug 27 '19 at 01:25

2 Answers2

2

First of all, wait condition needs a mutex, so you gotta give it one. That's what a wait condition is. It is the most low level signalling mechanism between threads in multi-threading, so it doesn't provide the "convenience" you seem to be looking for.

But you also need the mutex to get things work right. A wait condition might have a spurious wakeup, that is it could be woken up for "no reason" (google "wait condition spurious wakeup" to learn more). So you have to have some condition in there to check, and keep waiting if it's still not time to continue. And to avoid race conditions, that check has to be protected by mutex.

Snippets:

// wait
mDummy.lock();
mStopWaiting = false; // maybe here, if you want to make sure this waits in all cases
while (!mStopWaiting)
{
    // note that wait releases the mutex while waiting
    mStep1.wait(&mDummy);
}
mDummy.unlock();

// signal end of wait
mDummy.lock();
mStopWaiting = true;
mStep1.wakeOne(); // or wakeAll() maybe depending on other code
mDummy.unlock();

As you can see, that mutex isn't so dummy after all. Note that all access to mStopWaiting has to be protected by this mutex, not just here.

hyde
  • 60,639
  • 21
  • 115
  • 176
1

Imagine you want to wait for something to happen. Since that something has to happen in another thread (since this thread is waiting) it has to be protected in some way to avoid race conditions.

Imagine you use the following code:

  1. Acquire a lock.
  2. Check if the thing you want to wait for has happened.
  3. If it has, stop, you're done.
  4. If it hasn't, wait.

Oops. We're still holding the lock. There's no way the thing we're waiting for can happen because no other thread can access it.

Let's try again.

  1. Acquire a lock.
  2. Check if the thing you want to wait for has happened.
  3. If it has, stop, you're done.
  4. If it hasn't, release the lock and wait.

Oops. What if after we release the lock but before we wait, it happens. Then we'll be waiting for something that already happened.

So what we need for step 4 is an atomic "unlock and wait" operation. This releases the lock and waits without giving another thread a chance to sneak in and change things before we can start waiting.

If you don't need an atomic "unlock and wait" operation, don't use QWaitCondition. This is its sole purpose. It takes a QMutex so it knows what to unlock. That QMutex must protect whatever it is the thread is waiting for or your code will be vulnerable to the very race condition QWaitCondition exists to solve for you.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278