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. Understanding the thread/ signal-slot context of an object
Forum Updated to NodeBB v4.3 + New Features

Understanding the thread/ signal-slot context of an object

Scheduled Pinned Locked Moved Solved General and Desktop
3 Posts 3 Posters 275 Views 1 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.
  • F Offline
    F Offline
    f222
    wrote on last edited by
    #1

    Hello,

    When making multithreaded Qt applications with signal slot I tend to have trouble understanding which threads my objects are considered to be on (especially since variables/properties might not really be on a thread but just used exclusively by that thread).

    I am thinking about a special case today where I don't really know if I may have some atomicity/concurrency issue, UB, ...

    This is an oversimplified version of a code I'm refactoring so the logic is, I believe, quite bad and this is why I'm trying to understand before breaking anything .

    struct DataStorer
    {
        DataStorer(QString const &param)
            : baseData { param }
        {}
    
        void init (QString const &separator)
        {
            data = baseData.split(separator);
        }
    
        QStringList data;
    
    public slots:
        void updateData(/*someParams*/)
        {
            // update some data elements depending on the params*/
        }
    
    private:
        QString baseData;
    };
    
    class MyThread : public QThread
    {
    public:
        MyThread(QString const &param)
            : sharedData {param}
        {}
    
        void init()
        {
            if (!isRunning())
            {
                start();
            }
    
            sharedData.init("foobar");
    
            connect(
               this, &MyThread::dataUpdated,
               &sharedData, &DataStorer::updateData
            );
        }
    
        void run() final
        {
            // Sleep until init is done
            while (42)
            {
                // Do some stuff
                emit dataUpdated(/*someParam*/);
                // Do some stuff
                emit updateGUI(&sharedData);
            }
        }
    
    signals:
        void dataUpdated(/*someParams*/);
        void updateGUI(DataStorer const *data);
    
    private:
        DataStorer sharedData;
    };
    
    class MyWidget : public QWidget
    {
    
    public slots:
        void update(DataStorer const *data)
        {
            for (QString const &current: qAsConst(data->data))
            {
                /*update some models using current */
            }
        }
    };
    
    int main ()
    {
        MyWidget *widget {new MyWidget()};
        MyThread thread {"some string"};
    
        QObject::connect(
            &thread, &MyThread::updateGUI,
            widget, &MyWidget::update
        );
    
        // Setup a window with the widget in it
    
        thread.init();
    
        // run QApplication
    }
    

    (Q_OBJECT macro, parent transmission, virtual dtor, rule of 5, ... were voluntarily omitted to simply the code snippet).

    I first have some questions about MyThread properties context:

    • Since sharedData is a property of the class MyThread, is it considered to be in the same thread/context as the object of type MyThread that instantiated it or is it by default on the main/GUI thread (and would need to be moveTothread to be on the same thread as the object holding it )?
    • If it is the main/GUI thread (which I believe), do the other properties of MyThread need to be moveToThread (or do we not care since they are only used by the thread object) ?

    Now assuming that sharedData object's context is the same as the myThread object holding it:

    • I believe sending it in a slot via pointer (like what's done here) would create concurrency issue, using a reference (which would need to declare metatype) would solve this since the class would be shallow-copied between the signal/slot and a deep copy would be done if the thread modifies it while the UI is updating. Right ?
    • If the dataStorer now holds its data via pointer, then sending date via reference in signal/slot would not solve the concurrency issue because Qt COW deepcopy will just copy the pointer and not the pointed data.

    Is my understanding right or am I missing something ?

    jsulmJ 1 Reply Last reply
    0
    • F f222

      Hello,

      When making multithreaded Qt applications with signal slot I tend to have trouble understanding which threads my objects are considered to be on (especially since variables/properties might not really be on a thread but just used exclusively by that thread).

      I am thinking about a special case today where I don't really know if I may have some atomicity/concurrency issue, UB, ...

      This is an oversimplified version of a code I'm refactoring so the logic is, I believe, quite bad and this is why I'm trying to understand before breaking anything .

      struct DataStorer
      {
          DataStorer(QString const &param)
              : baseData { param }
          {}
      
          void init (QString const &separator)
          {
              data = baseData.split(separator);
          }
      
          QStringList data;
      
      public slots:
          void updateData(/*someParams*/)
          {
              // update some data elements depending on the params*/
          }
      
      private:
          QString baseData;
      };
      
      class MyThread : public QThread
      {
      public:
          MyThread(QString const &param)
              : sharedData {param}
          {}
      
          void init()
          {
              if (!isRunning())
              {
                  start();
              }
      
              sharedData.init("foobar");
      
              connect(
                 this, &MyThread::dataUpdated,
                 &sharedData, &DataStorer::updateData
              );
          }
      
          void run() final
          {
              // Sleep until init is done
              while (42)
              {
                  // Do some stuff
                  emit dataUpdated(/*someParam*/);
                  // Do some stuff
                  emit updateGUI(&sharedData);
              }
          }
      
      signals:
          void dataUpdated(/*someParams*/);
          void updateGUI(DataStorer const *data);
      
      private:
          DataStorer sharedData;
      };
      
      class MyWidget : public QWidget
      {
      
      public slots:
          void update(DataStorer const *data)
          {
              for (QString const &current: qAsConst(data->data))
              {
                  /*update some models using current */
              }
          }
      };
      
      int main ()
      {
          MyWidget *widget {new MyWidget()};
          MyThread thread {"some string"};
      
          QObject::connect(
              &thread, &MyThread::updateGUI,
              widget, &MyWidget::update
          );
      
          // Setup a window with the widget in it
      
          thread.init();
      
          // run QApplication
      }
      

      (Q_OBJECT macro, parent transmission, virtual dtor, rule of 5, ... were voluntarily omitted to simply the code snippet).

      I first have some questions about MyThread properties context:

      • Since sharedData is a property of the class MyThread, is it considered to be in the same thread/context as the object of type MyThread that instantiated it or is it by default on the main/GUI thread (and would need to be moveTothread to be on the same thread as the object holding it )?
      • If it is the main/GUI thread (which I believe), do the other properties of MyThread need to be moveToThread (or do we not care since they are only used by the thread object) ?

      Now assuming that sharedData object's context is the same as the myThread object holding it:

      • I believe sending it in a slot via pointer (like what's done here) would create concurrency issue, using a reference (which would need to declare metatype) would solve this since the class would be shallow-copied between the signal/slot and a deep copy would be done if the thread modifies it while the UI is updating. Right ?
      • If the dataStorer now holds its data via pointer, then sending date via reference in signal/slot would not solve the concurrency issue because Qt COW deepcopy will just copy the pointer and not the pointed data.

      Is my understanding right or am I missing something ?

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

      @f222 said in Understanding the thread/ signal-slot context of an object:

      Since sharedData is a property of the class MyThread, is it considered to be in the same thread/context as the object of type MyThread that instantiated it

      Yes, it is in the thread which instantiated MyThread, it is NOT in the thread MyThread is managing!

      "do the other properties of MyThread need to be moveToThread" - if they belong to that thread then yes. In general it is better to create all variables/objects inside run() - in that case they automatically belong to the new thread. In your case sharedData should not be member of MyThread. You can either make it pointer and allocate inside run() or make it local variable in run().

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

      1 Reply Last reply
      5
      • S Offline
        S Offline
        SimonSchroeder
        wrote on last edited by
        #3

        First of all, just to be clear: It only matters which thread an object is in if it is derived from QObject. This is because the only case where thread affinity matters (in this context) is for calling slots. Slots are always executed in the context of their associated thread when called through a signal/slot connection; i.e. if called directly through a regular function call they are executed in the context of the current thread.

        Write your slot like regular functions (concerning their parameters). If I'm not mistaken the signal will copy the function arguments if necessary. You need not be (too much) concerned with this.

        1 Reply Last reply
        1
        • F f222 has marked this topic as solved on

        • Login

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