Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Calling QWaitCondition::wakeOne() from inside or outside my critical section?
Qt 6.11 is out! See what's new in the release blog

Calling QWaitCondition::wakeOne() from inside or outside my critical section?

Scheduled Pinned Locked Moved Unsolved General and Desktop
14 Posts 4 Posters 4.2k Views 2 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • m.sueM m.sue

    Hi @Bart_Vandewoestyne

    What does queueNotEmpty contain? Threads? Why are they all blocked, as wakeOne suggests? If they are blocked by the mutex of the critical section one thread will be woken automatically by mutex.unlock() and the wakeOne-call would be superfluous.

    -Michael.

    B Offline
    B Offline
    Bart_Vandewoestyne
    wrote on last edited by
    #4

    @m.sue I'm afraid I'm not following you here, or I've not expressed myself clearly :-) The queueNotEmpty variable is of type QWaitCondition, it is not a container that contains things. The call to queueNotEmpty.wakeOne() is a call to signal that an endCommand was added to the queue and is now ready for processing/dequeueing.

    1 Reply Last reply
    0
    • jsulmJ jsulm

      @Bart_Vandewoestyne

      1. Outside should be fine as it is a local variable
      2. I would say it doesn't matter. If you do it inside then in the next line you do the unlock so there will be no dead-lock. But I would do it outside, because else it could happen that the next one is locked at mutex.lock(); until you do mutex.unlock();. This way you would avoid another thread being locked first before it can continue. But this should not make a big difference.
      B Offline
      B Offline
      Bart_Vandewoestyne
      wrote on last edited by
      #5

      @jsulm I think we agree on (1). As for (2), I have been skimming through some material at https://stackoverflow.com/questions/2763714/why-do-pthreads-condition-variable-functions-require-a-mutex and most of the examples there seem to put the queueNotEmpty.wakeOne();-alike calls inside the critical section. For as far as I understand, the reasoning is that queueNotEmpty is also a shared variable (it is a private member variable of the CommandHandler class, which is in my case a Singleton that is shared among two threads) and since it can therefore be accessed by different threads, it must be protected. So following this reasoning, I would say that queueNotEmpty.wakeOne() must be inside the critical section... but of course my reasoning could be wrong :-) Any further thoughts?

      jsulmJ 1 Reply Last reply
      0
      • B Bart_Vandewoestyne

        @jsulm I think we agree on (1). As for (2), I have been skimming through some material at https://stackoverflow.com/questions/2763714/why-do-pthreads-condition-variable-functions-require-a-mutex and most of the examples there seem to put the queueNotEmpty.wakeOne();-alike calls inside the critical section. For as far as I understand, the reasoning is that queueNotEmpty is also a shared variable (it is a private member variable of the CommandHandler class, which is in my case a Singleton that is shared among two threads) and since it can therefore be accessed by different threads, it must be protected. So following this reasoning, I would say that queueNotEmpty.wakeOne() must be inside the critical section... but of course my reasoning could be wrong :-) Any further thoughts?

        jsulmJ Offline
        jsulmJ Offline
        jsulm
        Lifetime Qt Champion
        wrote on last edited by
        #6

        @Bart_Vandewoestyne From the documentation: "Note: All functions in this class are thread-safe."
        http://doc.qt.io/qt-5.9/qwaitcondition.html#wakeOne
        So, it should not matter.

        https://forum.qt.io/topic/113070/qt-code-of-conduct

        B 1 Reply Last reply
        0
        • jsulmJ jsulm

          @Bart_Vandewoestyne From the documentation: "Note: All functions in this class are thread-safe."
          http://doc.qt.io/qt-5.9/qwaitcondition.html#wakeOne
          So, it should not matter.

          B Offline
          B Offline
          Bart_Vandewoestyne
          wrote on last edited by
          #7

          @jsulm I'm still not totally convinced, so let the discussion continue! ;-) I think the question boils down to whether

          queue.clear();
          queue.enqueue(endCommand); 
          

          and

          queueNotEmpty.wakeOne();
          

          must be considered as one atomic operation or not. If 'yes', then the call to wakeOne() must be inside the critical section. If 'not', it can be left outside. I do think that in most cases it must be considered as an atomic operation, because 'updating the queue and then signalling that something is available' looks like something that must be considered 'atomic' to me... (please correct me if I'm wrong, folks!)

          I've also came across this answer to a question on the Qt-interest list from 2009: http://lists.qt-project.org/pipermail/qt-interest-old/2009-June/008281.html where the author also seems to suggest to move the calls to wakeOne() inside the critical section for reasons of avoiding data races...

          m.sueM kshegunovK 2 Replies Last reply
          0
          • B Bart_Vandewoestyne

            @jsulm I'm still not totally convinced, so let the discussion continue! ;-) I think the question boils down to whether

            queue.clear();
            queue.enqueue(endCommand); 
            

            and

            queueNotEmpty.wakeOne();
            

            must be considered as one atomic operation or not. If 'yes', then the call to wakeOne() must be inside the critical section. If 'not', it can be left outside. I do think that in most cases it must be considered as an atomic operation, because 'updating the queue and then signalling that something is available' looks like something that must be considered 'atomic' to me... (please correct me if I'm wrong, folks!)

            I've also came across this answer to a question on the Qt-interest list from 2009: http://lists.qt-project.org/pipermail/qt-interest-old/2009-June/008281.html where the author also seems to suggest to move the calls to wakeOne() inside the critical section for reasons of avoiding data races...

            m.sueM Offline
            m.sueM Offline
            m.sue
            wrote on last edited by
            #8

            Hi @Bart_Vandewoestyne

            thread-safe means that all such operations are atomical as they are safe from other threads to interfere with their actions by definition.

            -Michael.

            jsulmJ B 2 Replies Last reply
            0
            • m.sueM m.sue

              Hi @Bart_Vandewoestyne

              thread-safe means that all such operations are atomical as they are safe from other threads to interfere with their actions by definition.

              -Michael.

              jsulmJ Offline
              jsulmJ Offline
              jsulm
              Lifetime Qt Champion
              wrote on last edited by
              #9

              @m.sue @Bart_Vandewoestyne means that the combination of those 3 calls must be atomic. The combination is not automatically atomic just because each call is atomic.
              But I don't see any reason why these 3 calls must be atomic - only access to the queue must be serialized. queueNotEmpty.wakeOne(); will be executed in any case whether it is inside the lock or outside doesn't matter in my opinion.

              https://forum.qt.io/topic/113070/qt-code-of-conduct

              1 Reply Last reply
              0
              • m.sueM m.sue

                Hi @Bart_Vandewoestyne

                thread-safe means that all such operations are atomical as they are safe from other threads to interfere with their actions by definition.

                -Michael.

                B Offline
                B Offline
                Bart_Vandewoestyne
                wrote on last edited by
                #10

                @m.sue Consider the example from http://doc.qt.io/qt-4.8/qwaitcondition.html#details, more specifically the part

                forever {
                    getchar();
                
                    mutex.lock();
                    // Sleep until there are no busy worker threads
                    while (count > 0) {
                        mutex.unlock();
                        sleep(1);
                        mutex.lock();
                    }
                    keyPressed.wakeAll();
                    mutex.unlock();
                }
                

                For as far as I understand, the topmost lock/unlock is for thread-safe access to the shared count variable, but what about the second lock/unlock? Does it only serve to protect count, or does it also protect the keyPressed condition variable? Is it intentional that keyPressed.wakeAll() is also locked, or can it be moved to after the last mutex.unlock() call? According to your reasoning, it could, but I'm not 100% convinced...

                B 1 Reply Last reply
                0
                • B Bart_Vandewoestyne

                  @jsulm I'm still not totally convinced, so let the discussion continue! ;-) I think the question boils down to whether

                  queue.clear();
                  queue.enqueue(endCommand); 
                  

                  and

                  queueNotEmpty.wakeOne();
                  

                  must be considered as one atomic operation or not. If 'yes', then the call to wakeOne() must be inside the critical section. If 'not', it can be left outside. I do think that in most cases it must be considered as an atomic operation, because 'updating the queue and then signalling that something is available' looks like something that must be considered 'atomic' to me... (please correct me if I'm wrong, folks!)

                  I've also came across this answer to a question on the Qt-interest list from 2009: http://lists.qt-project.org/pipermail/qt-interest-old/2009-June/008281.html where the author also seems to suggest to move the calls to wakeOne() inside the critical section for reasons of avoiding data races...

                  kshegunovK Offline
                  kshegunovK Offline
                  kshegunov
                  Moderators
                  wrote on last edited by kshegunov
                  #11

                  You have a thread-safe queue, as far as I can deduce from the snippet. The answer to your questions are:

                  1. Doesn't matter in principle, but if initialization is heavy you should move it out of the protected section. It's a good idea to keep the mutex locked for as small amount of time as possible.
                  2. Should be inside the mutex-locked section. This will wake the consumers to dequeue the data. In the other part of the code where dequeue is happening the waiting on the condition should be outside of the mutex-protected section.
                    Edit: This is wrong.
                    It's how you'd do it if you used a semaphore (which is internally realized in Qt with a mutex and a wait condition) - see the snippet here.

                  Read and abide by the Qt Code of Conduct

                  B 1 Reply Last reply
                  0
                  • B Bart_Vandewoestyne

                    @m.sue Consider the example from http://doc.qt.io/qt-4.8/qwaitcondition.html#details, more specifically the part

                    forever {
                        getchar();
                    
                        mutex.lock();
                        // Sleep until there are no busy worker threads
                        while (count > 0) {
                            mutex.unlock();
                            sleep(1);
                            mutex.lock();
                        }
                        keyPressed.wakeAll();
                        mutex.unlock();
                    }
                    

                    For as far as I understand, the topmost lock/unlock is for thread-safe access to the shared count variable, but what about the second lock/unlock? Does it only serve to protect count, or does it also protect the keyPressed condition variable? Is it intentional that keyPressed.wakeAll() is also locked, or can it be moved to after the last mutex.unlock() call? According to your reasoning, it could, but I'm not 100% convinced...

                    B Offline
                    B Offline
                    Bart_Vandewoestyne
                    wrote on last edited by
                    #12

                    Also consider the example code at http://doc.qt.io/qt-4.8/qt-threads-waitconditions-example.html
                    There, the calls to bufferNotEmpty.wakeAll() and bufferNotFull.wakeAll() are inside the critical section. My question is whether that is really necessary or not, or can those calls also be moved one statement down, to right after the mutex.unlock() statement?

                    I am more and more convinced that they should be in the critical section, but I can't really explain why. I currently don't have time to play with the waitconditions example code to see if things go wrong if you move the wakeAll() calls outside the critical sections, but just out of curiosity, I might experiment with it if I have some time...

                    kshegunovK 1 Reply Last reply
                    0
                    • kshegunovK kshegunov

                      You have a thread-safe queue, as far as I can deduce from the snippet. The answer to your questions are:

                      1. Doesn't matter in principle, but if initialization is heavy you should move it out of the protected section. It's a good idea to keep the mutex locked for as small amount of time as possible.
                      2. Should be inside the mutex-locked section. This will wake the consumers to dequeue the data. In the other part of the code where dequeue is happening the waiting on the condition should be outside of the mutex-protected section.
                        Edit: This is wrong.
                        It's how you'd do it if you used a semaphore (which is internally realized in Qt with a mutex and a wait condition) - see the snippet here.
                      B Offline
                      B Offline
                      Bart_Vandewoestyne
                      wrote on last edited by
                      #13

                      @kshegunov said in Calling QWaitCondition::wakeOne() from inside or outside my critical section?:

                      [...] 2) Should be inside the mutex-locked section. This will wake the consumers to dequeue the data. In the other part of the code where dequeue is happening the waiting on the condition should be outside of the mutex-protected section.[...]

                      I'm not sure if I understand your answer correctly. Our code looks something like:

                      mutex.lock();
                      if (queue.isEmpty())
                      {
                          queueNotEmpty.wait(&mutex);
                      }
                      const auto command = queue.dequeue();
                      mutex.unlock();
                      

                      So the wait() is inside the mutex-protected section. The documentation at http://doc.qt.io/qt-4.8/qwaitcondition.html#wait also clearly states

                      The mutex must be initially locked by the calling thread. If mutex is not in a locked state, this function returns immediately.

                      and the example at http://doc.qt.io/qt-4.8/qwaitcondition.html#details also has the wait() call inside the mutex-protected section.

                      Or did i interpret your answer incorrectly?

                      1 Reply Last reply
                      0
                      • B Bart_Vandewoestyne

                        Also consider the example code at http://doc.qt.io/qt-4.8/qt-threads-waitconditions-example.html
                        There, the calls to bufferNotEmpty.wakeAll() and bufferNotFull.wakeAll() are inside the critical section. My question is whether that is really necessary or not, or can those calls also be moved one statement down, to right after the mutex.unlock() statement?

                        I am more and more convinced that they should be in the critical section, but I can't really explain why. I currently don't have time to play with the waitconditions example code to see if things go wrong if you move the wakeAll() calls outside the critical sections, but just out of curiosity, I might experiment with it if I have some time...

                        kshegunovK Offline
                        kshegunovK Offline
                        kshegunov
                        Moderators
                        wrote on last edited by kshegunov
                        #14

                        @Bart_Vandewoestyne said in Calling QWaitCondition::wakeOne() from inside or outside my critical section?:

                        Also consider the example code at

                        It's correct. That is to say:
                        Consumer: Wait for data to arrive before dequeueing it, then consume the data and free the producer(s).
                        Producer: Enqueue the data and wake up the consumers, wait for the consumers if you've produced too much.

                        Basically:
                        bufferNotFull.wait should be in the producer before adding data, respectively bufferNotFull.wakeAll - in the consumers AFTER the data has been dequeued. In reverse for the bufferNotEmpty wait condition (like in the example).

                        Edit:
                        And I didn't answer your question, sorry.

                        My question is whether that is really necessary or not, or can those calls also be moved one statement down, to right after the mutex.unlock() statement?

                        No, they're supposed to be exactly as shown in the example you sourced.

                        I'm not sure if I understand your answer correctly. Our code looks something like:

                        mutex.lock();
                        if (queue.isEmpty())
                        {
                            queueNotEmpty.wait(&mutex);
                        }
                        const auto command = queue.dequeue();
                        mutex.unlock();
                        

                        That is correct. And it is correct, because wait will unlock the mutex before waiting to be signaled. After it's been woken up it will relock it.

                        Edit 2: I'm still sleeping :(

                        Your code should be like this:

                        mutex.lock();
                        queueNotEmpty.wait(&mutex);
                        if (queue.isEmpty())
                            ;   // Nothing more to do, exit threads.
                        
                        const auto command = queue.dequeue();
                        mutex.unlock();
                        

                        Read and abide by the Qt Code of Conduct

                        1 Reply Last reply
                        1

                        • Login

                        • Login or register to search.
                        • First post
                          Last post
                        0
                        • Categories
                        • Recent
                        • Tags
                        • Popular
                        • Users
                        • Groups
                        • Search
                        • Get Qt Extensions
                        • Unsolved