2

I have a class WorkerThreadManager that creates and owns a number of Qt threads:

class WorkerThread : public QThread
{
    Q_OBJECT
public:
    WorkerThread() : QThread()
    {
        start();
    }

    virtual ~SearchCacheWorkerThread()
    {
        quit();
        wait();
    }
    
    virtual void run() Q_DECL_OVERRIDE
    {
        // fetch from mysql
        // process results
        // append many new items to results_
    
        quit();
    }

private:
    QList<int> results_;
};
typedef QSharedPointer<WorkerThread> WorkerThreadPtr;

class WorkerThreadManager : QObject
{
    Q_OBJECT
public:
    WorkerThreadManager(const int numThreads) : QObject()
    {
        for ( int i = 0; i < numThreads; ++i )
        {
            threads_ << WorkerThreadPtr( new WorkerThread(), &QObject::deleteLater );
        }
    }
    virtual ~WorkerThreadManager() {}

private:
    QList<WorkerThreadPtr> threads_;

When I destroy the WorkerThreadManager object, I don't see the memory being released.

I found from here that the deleteLater won't do anything if the thread has exited the event loop. So I changed the code to remove all calls to quit() in order to ensure a thread stays in the event loop until deleteLater is called. But I still get the memory leak.

  1. What's the correct way to destroy the threads (preferrably with the use of smart pointers)?
  2. How do I ensure that the threads will finish before they get destroyed, even if their destructor is called?
A. Paschos
  • 63
  • 7
  • Do you actually need a custom deleter here? When the shared pointer is destroyed, it should delete its contents right away, assuming there are no other shared pointers referencing it. – JarMan May 15 '23 at 16:00
  • https://www.qt.io/blog/2010/06/17/youre-doing-it-wrong – Marek R May 15 '23 at 16:25
  • Also I recommend not using threads. In 99% of cases signals and slots are working great on single thread. In other cases use of QtConcurrent is more handy then `QThread` – Marek R May 15 '23 at 16:28
  • There is a specific pattern for deleting QObjects living in a thread which terminates: https://doc.qt.io/qt-6/qthread.html#details – hyde May 15 '23 at 16:44
  • @JarMan and how do I get a guarantee that there's no slot being handled at the point of the deletion? Isn't that the point of `deleteLater`? – A. Paschos May 16 '23 at 10:20
  • @MarekR I totally agree. But this is production code and it wasn't written by me. I need a very good reason to justify changing the framework instead of fixing it. – A. Paschos May 16 '23 at 10:21
  • Living aside best practices, isn't `deleteLater()` supposed to be safer than plain `delete`? If not, why? – A. Paschos May 16 '23 at 10:28

1 Answers1

1

As JarMan mentioned in a comment, it doesn't seem necessary (at least from the provided snippet) to have a custom deleter for your shared pointer.

If you've already tried removing the deleter, and you still observed the problem, it's possible that someone is still holding a reference to the thread object. To be absolutely sure that's not the case, try using a QScopedPointer instead, which is similar to std::unique_ptr.

This should provide a runtime error if you're passing your smart pointer by value and some other code is attempting to store it, and then you'll know where you should switch to using a raw pointer type (WorkerThread*).

It's hard to know for sure without more code, but a common cause for this issue is capturing a shared pointer by value in a lambda used in a signal/slot connection. Based on what you've provided, is it possible you've done this? I see your workers have the member results_, are you consuming these results on the main thread through a signal emitted from your worker?

Lastly, as Marek R mentioned in the comments, it would be a much safer idea to use worker objects (described in the Qt blog thread they linked) instead of subclassing QThread. QtConcurrent is also an excellent choice if you don't need much control over the thread and only want some calculations to occur in another thread.

dylan
  • 68
  • 7
  • Thank you for the answer. At this point I'm trying to understand the Qt framework better so that I eliminate cases for the memory leak. The `results_` are consumed by a handler thread that tries to read them. The main thread though is the one that owns the manager objects. I know the worker objects are a better / the suggested way but at this point I'm trying to find out where the problem is. Is it safe to use `deleteLater()` with smart pointers for QThread subclasses? If not, why? – A. Paschos May 16 '23 at 11:17
  • Also, can I safely call the destructor of the manager (and subsequently of the threads) from a handler thread (as in **not** the thread that created the object)? – A. Paschos May 16 '23 at 11:25
  • 1
    "`results_` are consumed by a handler thread that tries to read them" Are you establishing a connection to a signal from your workers? Is the slot its connected to a lambda? Please double check that you're not prolonging the life of your shared pointers. If you haven't yet, try the QScopedPointer trick first. `deleteLater()` is for when you want an object to be deleted in the next event loop cycle, which prevents any currently queued events from trying to use a null object. You can use it in a smart pointer, but since this is a shared pointer, this doesn't make much sense. – dylan May 16 '23 at 16:13
  • 1
    Shared pointers, by default, ensure that if some code is still using the object, it will not be deleted. Using `deleteLater()` just prolongs the lifetime by one more event loop cycle before deleting it, which probably doesn't do anything useful in this case. And **no**, you should not manually call a destructor on an object, especially from another thread. This is what `deleteLater()` would be useful for. The destruction of an object should be done on the thread in which it lives, and `deleteLater()` creates an event to schedule the object for deletion in its own thread. – dylan May 16 '23 at 16:17
  • Thanks for the help, I found where the problem was. There's still a memory leak but that's a separate issue. At least I got a better idea of how the Qt threads are managed. – A. Paschos May 17 '23 at 08:51