c++multithreadingqtshared-ptrqsharedpointer

life cycle of QSharedPointer or std::shared_ptr


in my application

I've a MainWindow (which is a QtMainWindow class) and a Acquisiton class (which is a QThread class)

Here my very simplified Acquisiton class

//entry point of the thread
void Acquisition::run ()
{
    uint8_t*                image_addr;
    QSharedPointer<uint8_t> image(new uint8_t[IMG_SIZE]);     

    for (;;)
    {
        if (isInterruptionRequested())
            return;


        // here, usb_read() give me the adress of a elem in the ring buffer 
        img_addr = usb_read(...);

        // the ring buffer can possibly be rewritten on the next usb_read() (if bufferlength = 1) so I copy the data into my QSharedPointer

        std::memcpy(image.data(), image_addr, sizeof(IMG_SIZE));

       // I send this image 
       emit imageSent(image);
    }
}

and in my MainWindow I've

// the slot for the signal imageSent
void newImage(QSharedPointer<uint8_t> image)
{
    // process and draw image
}

I don't understand the lifecycle of the QSharedPointer (and std::shared_ptr (imagine the samecode with std::shared_ptr)

Does my QSharedPointer is always valid ? What append if during processing (MainWindow), the usb_read() occurs and the memcpy write on my image.

In a related question: Waiting slots to be executed before quitting

I see that QSharedPointer keeps my data valid if the acquisition threads stop during data is processing.

In this case, is my signal canceled, my values are copied somewhere or the thread wait for my MainWindow to finish processing ?

Thanks


Solution

  • As it was already written in Resurrection's answer shared pointers are valid as long as they are at least referenced at one location.

    In your case you will only have once instance of the shared pointer, which is the one you create at the start of the Acquisition thread. It is referenced in the Acquisition thread as well as in the signal handlers that will be called by QT. As you have only one shared pointer (with one byte array in it) you are now updating the same data buffer on each acquisition and overwrite it, potentially at the same moment when another thread has not yet read it. You can however easily fix that by creating a new shared pointer instance for each sample and pass that one to the other thread in the signal.

    The following small change should do it:

    //entry point of the thread
    void Acquisition::run ()
    {
        uint8_t*                image_addr;
    
        for (;;)
        {
            if (isInterruptionRequested())
                return;
    
    
            // here, usb_read() give me the adress of a elem in the ring buffer 
            img_addr = usb_read(...);
    
            // Create a fresh shared pointer in the scope
            QSharedPointer<uint8_t> image(new uint8_t[IMG_SIZE]);   
    
            // the ring buffer can possibly be rewritten on the next usb_read() (if bufferlength = 1) so I copy the data into my QSharedPointer
    
            std::memcpy(image.data(), image_addr, sizeof(IMG_SIZE));
    
           // I send this image 
           emit imageSent(image);
        }
    }
    

    And regarding the cancellation and signaling: When you call emit signals between different threads in QT then by default a queued connection will be used. This means on the emitting thread the data and the handler that should be called will be put in a queue. The data here is your shared pointer. The queue will held it alive, even if the acquisition thread finishes. Then when the other thread kicks in (MainThread, etc.) the data will be dequeued and the signal handler will be called with it.