Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. PyQT5 QThread: The best way to access shared objects
Forum Update on Monday, May 27th 2025

PyQT5 QThread: The best way to access shared objects

Scheduled Pinned Locked Moved Unsolved Qt for Python
6 Posts 4 Posters 3.4k Views
  • 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 Offline
    M Offline
    mitaa257
    wrote on 7 Oct 2020, 11:17 last edited by
    #1

    Hi, I am having trouble understanding some stuff about QThread performance. I am developing small web cam app with PyQT5, in which I used separate Thread to capture images with cv2 library in infinite loop, and send it as a signal parameter to QMainWindow (GUI thread) to be displayed. When I do a resize of the main window, QMainWindow is sending a signal with new size as a parameter to the working thread, informing it to scale the taken images. Everything is working good, but I am wondering if I have implemented it in a safe way, because both GUI and worker thread are accessing same QSize variable (which is created in the main GUI thread) simultaneously. I don't know if I should put mutexes and where. Here is the code that I created.

    View class:

    class View(QtWidgets.QMainWindow):
      reSize = pyqtSignal(QSize)
      def __init__(self):
          super(View,self).__init__()
          uic.loadUi('ui/mainwindow.ui',self)
          self.canvas_label  = self.findChild(QtWidgets.QLabel, 'canvas')
          self.backgroundWorker = BackgroundWorker(self.canvas_label.size())
          self.backgroundWorker.changePixmap.connect(self.setImage)
          self.reSize.connect(self.backgroundWorker.scaled)
          self.backgroundWorker.start()
    
      @pyqtSlot(QImage)
      def setImage(self, image):
          self.canvas_label.setPixmap(QPixmap.fromImage(image))
    
      def resizeEvent(self, event):
        self.reSize.emit(self.findChild(QtWidgets.QLabel, 'canvas').size())
    

    BackgroundWorker:

    class BackgroundWorker(QThread):
    
        changePixmap = pyqtSignal(QImage)
        
        def __init__(self,img_dimensions):
            super().__init__()
            self.img_dimensions = img_dimensions
            self.mutex = QMutex()
    
        def run(self):
            cap = cv2.VideoCapture(0)
            while True:
                ret, frame = cap.read()
                if ret:
                    rgbImage = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                    h, w, ch = rgbImage.shape
                    bytesPerLine = ch * w
                    convertToQtFormat = QImage(rgbImage.data, w, h, bytesPerLine, QImage.Format_RGB888)
                    p = convertToQtFormat.scaled(self.img_dimensions.width(), self.img_dimensions.height())
                    self.changePixmap.emit(p)
    
        def scaled(self, scaled_size):
            self.img_dimensions = scaled_size
    

    I was wondering would it be correct to create QMutex in Background worker constructor, and use it to lock like this:

     self.mutex.lock()
     p = convertToQtFormat.scaled(self.img_dimensions.width(), self.img_dimensions.height())
     self.mutex.unlock()
    

    in run() function, and in scaled function like this:

     def scaled(self, scaled_size):
          self.mutex.lock()
          self.img_dimensions = scaled_size
          self.mutex.unlock()
    
    J 1 Reply Last reply 7 Oct 2020, 12:46
    0
    • M mitaa257
      7 Oct 2020, 11:17

      Hi, I am having trouble understanding some stuff about QThread performance. I am developing small web cam app with PyQT5, in which I used separate Thread to capture images with cv2 library in infinite loop, and send it as a signal parameter to QMainWindow (GUI thread) to be displayed. When I do a resize of the main window, QMainWindow is sending a signal with new size as a parameter to the working thread, informing it to scale the taken images. Everything is working good, but I am wondering if I have implemented it in a safe way, because both GUI and worker thread are accessing same QSize variable (which is created in the main GUI thread) simultaneously. I don't know if I should put mutexes and where. Here is the code that I created.

      View class:

      class View(QtWidgets.QMainWindow):
        reSize = pyqtSignal(QSize)
        def __init__(self):
            super(View,self).__init__()
            uic.loadUi('ui/mainwindow.ui',self)
            self.canvas_label  = self.findChild(QtWidgets.QLabel, 'canvas')
            self.backgroundWorker = BackgroundWorker(self.canvas_label.size())
            self.backgroundWorker.changePixmap.connect(self.setImage)
            self.reSize.connect(self.backgroundWorker.scaled)
            self.backgroundWorker.start()
      
        @pyqtSlot(QImage)
        def setImage(self, image):
            self.canvas_label.setPixmap(QPixmap.fromImage(image))
      
        def resizeEvent(self, event):
          self.reSize.emit(self.findChild(QtWidgets.QLabel, 'canvas').size())
      

      BackgroundWorker:

      class BackgroundWorker(QThread):
      
          changePixmap = pyqtSignal(QImage)
          
          def __init__(self,img_dimensions):
              super().__init__()
              self.img_dimensions = img_dimensions
              self.mutex = QMutex()
      
          def run(self):
              cap = cv2.VideoCapture(0)
              while True:
                  ret, frame = cap.read()
                  if ret:
                      rgbImage = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                      h, w, ch = rgbImage.shape
                      bytesPerLine = ch * w
                      convertToQtFormat = QImage(rgbImage.data, w, h, bytesPerLine, QImage.Format_RGB888)
                      p = convertToQtFormat.scaled(self.img_dimensions.width(), self.img_dimensions.height())
                      self.changePixmap.emit(p)
      
          def scaled(self, scaled_size):
              self.img_dimensions = scaled_size
      

      I was wondering would it be correct to create QMutex in Background worker constructor, and use it to lock like this:

       self.mutex.lock()
       p = convertToQtFormat.scaled(self.img_dimensions.width(), self.img_dimensions.height())
       self.mutex.unlock()
      

      in run() function, and in scaled function like this:

       def scaled(self, scaled_size):
            self.mutex.lock()
            self.img_dimensions = scaled_size
            self.mutex.unlock()
      
      J Offline
      J Offline
      jsulm
      Lifetime Qt Champion
      wrote on 7 Oct 2020, 12:46 last edited by
      #2

      @mitaa257 said in PyQT5 QThread: The best way to access shared objects:

      same QSize variable

      Which size variable?
      As far as I can see you do this:

      self.reSize.emit(self.findChild(QtWidgets.QLabel, 'canvas').size())
      

      Which should be safe as for queued connections the parameters are copied if I'm not mistaken. So you are not accessing anything shared.
      See https://doc.qt.io/qt-5/qt.html#ConnectionType-enum
      "With queued connections, the parameters must be of types that are known to Qt's meta-object system, because Qt needs to copy the arguments to store them in an event behind the scenes."

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

      1 Reply Last reply
      1
      • M Offline
        M Offline
        mitaa257
        wrote on 7 Oct 2020, 14:48 last edited by
        #3

        If I understood it good (according to this source https://wiki.qt.io/Threads_Events_QObjects) this is not queued connection, but a direct one, because in the run() method we got endless loop, so the thread's event loop could never process the event. I was referring to the variable img_dimensions in BackgroundWorker class. I think it is accessed in the run method by a worker thread, and trough a scaled function too.

        J 1 Reply Last reply 7 Oct 2020, 15:32
        0
        • M mitaa257
          7 Oct 2020, 14:48

          If I understood it good (according to this source https://wiki.qt.io/Threads_Events_QObjects) this is not queued connection, but a direct one, because in the run() method we got endless loop, so the thread's event loop could never process the event. I was referring to the variable img_dimensions in BackgroundWorker class. I think it is accessed in the run method by a worker thread, and trough a scaled function too.

          J Offline
          J Offline
          JonB
          wrote on 7 Oct 2020, 15:32 last edited by JonB 10 Jul 2020, 15:36
          #4

          @mitaa257 said in PyQT5 QThread: The best way to access shared objects:

          If I understood it good (according to this source https://wiki.qt.io/Threads_Events_QObjects) this is not queued connection, but a direct one

          Why do you say this? The signalling thread is different from the slot one, so the default will be queued. It is the connect() which implements this, and that does not know whether the thread does or doesn't have an event loop. The event loop is required in the slot thread to receive it (for queued), but I don't think there is any requirement in the emitting thread to have an event loop(?) (Unless perchance PyQt requires that, but I don't think Qt does.)

          1 Reply Last reply
          0
          • M Offline
            M Offline
            mitaa257
            wrote on 7 Oct 2020, 15:52 last edited by mitaa257 10 Jul 2020, 16:01
            #5

            I think that just the stuff that happens inside run() function is happening in other thread, look at the examples in the link that I provided. I don't think that the signaling thread and the slot one, in this case, are different, because they have the same thread affinity. How is it possible for resize event to get processed in BackgroundWorker slot, if these two threads are different, and BackroundWorker is stuck in endless loop ?

            1 Reply Last reply
            0
            • SGaistS Offline
              SGaistS Offline
              SGaist
              Lifetime Qt Champion
              wrote on 7 Oct 2020, 18:30 last edited by
              #6

              Hi,

              You are correct, since you subclassed QThread, the slots are called in the main thread but the run method in the new thread.

              There are different strategies that can be used to avoid the overhead of a mutex on each loop. You might want to consider a dirty flag so you only read the new value once after is has changed. You still protect the access to your size variable but you access it less often.

              Interested in AI ? www.idiap.ch
              Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

              1 Reply Last reply
              1

              1/6

              7 Oct 2020, 11:17

              • Login

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