File indexing completed on 2025-03-09 03:56:12

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2010-04-13
0007  * Description : Thread object scheduling
0008  *
0009  * SPDX-FileCopyrightText: 2010-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "threadmanager.h"
0016 
0017 // Qt includes
0018 
0019 #include <QEventLoop>
0020 #include <QMutex>
0021 #include <QMutexLocker>
0022 #include <QPair>
0023 #include <QThread>
0024 #include <QThreadPool>
0025 #include <QWaitCondition>
0026 
0027 // Local includes
0028 
0029 #include "digikam_debug.h"
0030 #include "dynamicthread.h"
0031 #include "workerobject.h"
0032 
0033 namespace Digikam
0034 {
0035 
0036 class Q_DECL_HIDDEN ParkingThread : public QThread
0037 {
0038     Q_OBJECT
0039 
0040 public:
0041 
0042     explicit ParkingThread(QObject* const parent = nullptr)
0043         : QThread(parent),
0044           running(true)
0045     {
0046         start();
0047     }
0048 
0049     ~ParkingThread() override
0050     {
0051         running = false;
0052         condVar.wakeAll();
0053         wait();
0054     }
0055 
0056     void parkObject(QObject* const object)
0057     {
0058         object->moveToThread(this);
0059         QMutexLocker locker(&mutex);
0060         condVar.wakeAll();
0061     }
0062 
0063     void moveToCurrentThread(QObject* const parkedObject)
0064     {
0065         if (parkedObject->thread() == QThread::currentThread())
0066         {
0067             return;
0068         }
0069 
0070         // We have 1. The current thread
0071         //         2. ParkingThread's thread
0072         //         3. The parkedObject's thread, subject to change.
0073 
0074         QMutexLocker locker(&mutex);
0075 
0076         // first, wait until the object has been parked in ParkingThread by its owning thread.
0077 
0078         while (parkedObject->thread() != this)
0079         {
0080             condVar.wait(&mutex);
0081         }
0082 
0083         QThread* const targetThread = QThread::currentThread();
0084 
0085         // then, now that it's parked in ParkingThread, make ParkingThread move it to the current thread.
0086 
0087         todo << qMakePair(parkedObject, targetThread);
0088         condVar.wakeAll();
0089 
0090         // wait until ParkingThread has pushed the object to current thread
0091 
0092         while (parkedObject->thread() != targetThread)
0093         {
0094             condVar.wait(&mutex);
0095         }
0096     }
0097 
0098     void run() override
0099     {
0100         /* The quirk here is that this thread never runs an event loop.
0101          * That means events queued for parked object are only emitted when
0102          * these object have been moved to their own thread.
0103          */
0104         while (running)
0105         {
0106             QList<TodoPair> copyTodo;
0107             {
0108                 QMutexLocker locker(&mutex);
0109                 condVar.wakeAll();
0110 
0111                 if (todo.isEmpty())
0112                 {
0113                     condVar.wait(&mutex);
0114                     continue;
0115                 }
0116                 else
0117                 {
0118                     copyTodo = todo;
0119                     todo.clear();
0120                 }
0121             }
0122 
0123             Q_FOREACH (const TodoPair& pair, copyTodo)
0124             {
0125                 pair.first->moveToThread(pair.second);
0126             }
0127         }
0128     }
0129 
0130 public:
0131 
0132     volatile bool                     running;
0133     typedef QPair<QObject*, QThread*> TodoPair;
0134     QMutex                            mutex;
0135     QWaitCondition                    condVar;
0136     QList<TodoPair>                   todo;
0137 };
0138 
0139 // --------------------------------------------------------------------------------------------------
0140 
0141 class Q_DECL_HIDDEN WorkerObjectRunnable : public QRunnable
0142 {
0143 public:
0144 
0145     WorkerObjectRunnable(WorkerObject* const object, ParkingThread* const parkingThread);
0146 
0147 protected:
0148 
0149     WorkerObject*  object;
0150     ParkingThread* parkingThread;
0151 
0152 protected:
0153 
0154     void run() override;
0155 
0156 private:
0157 
0158     Q_DISABLE_COPY(WorkerObjectRunnable)
0159 };
0160 
0161 // --------------------------------------------------------------------------------------------------
0162 
0163 WorkerObjectRunnable::WorkerObjectRunnable(WorkerObject* const object, ParkingThread* const parkingThread)
0164     : object       (object),
0165       parkingThread(parkingThread)
0166 {
0167     setAutoDelete(true);
0168 }
0169 
0170 void WorkerObjectRunnable::run()
0171 {
0172     if (!object)
0173     {
0174         return;
0175     }
0176 
0177     // if another thread should still be running, wait until the object is parked in ParkingThread
0178 
0179     parkingThread->moveToCurrentThread(object);
0180 
0181     // The object is in state Scheduled or Deactivating now.
0182     // It won't be deleted until Inactive, and as long a runnable is set.
0183 
0184     object->addRunnable(this);
0185 
0186     Q_EMIT object->started();
0187 
0188     if (object->transitionToRunning())
0189     {
0190         QThread::Priority previousPriority = QThread::currentThread()->priority();
0191 
0192         if (object->priority() != QThread::InheritPriority)
0193         {
0194             QThread::currentThread()->setPriority(object->priority());
0195         }
0196 
0197         QEventLoop loop;
0198         object->setEventLoop(&loop);
0199         loop.exec();
0200         object->setEventLoop(nullptr);
0201 
0202         if (previousPriority != QThread::InheritPriority)
0203         {
0204             QThread::currentThread()->setPriority(previousPriority);
0205         }
0206     }
0207 
0208     object->transitionToInactive();
0209 
0210     Q_EMIT object->finished();
0211 
0212     // if this is rescheduled, it will wait in the other thread at moveToCurrentThread() above until we park
0213 
0214     parkingThread->parkObject(object);
0215 
0216     // now, free the object - in case it wants to get deleted
0217 
0218     object->removeRunnable(this);
0219 }
0220 
0221 // -------------------------------------------------------------------------------------------------
0222 
0223 class Q_DECL_HIDDEN ThreadManager::Private
0224 {
0225 public:
0226 
0227     Private()
0228       : parkingThread(nullptr),
0229         pool         (nullptr)
0230     {
0231     }
0232 
0233     ParkingThread* parkingThread;
0234     QThreadPool*   pool;
0235 
0236 public:
0237 
0238     void changeMaxThreadCount(int diff)
0239     {
0240         pool->setMaxThreadCount(pool->maxThreadCount() + diff);
0241     }
0242 };
0243 
0244 // -------------------------------------------------------------------------------------------------
0245 
0246 class Q_DECL_HIDDEN ThreadManagerCreator
0247 {
0248 public:
0249 
0250     ThreadManager object;
0251 };
0252 
0253 Q_GLOBAL_STATIC(ThreadManagerCreator, creator)
0254 
0255 // -------------------------------------------------------------------------------------------------
0256 
0257 ThreadManager* ThreadManager::instance()
0258 {
0259     return (&creator->object);
0260 }
0261 
0262 ThreadManager::ThreadManager()
0263     : d(new Private)
0264 {
0265     d->parkingThread = new ParkingThread(this);
0266     d->pool          = new QThreadPool(this);
0267 
0268     d->pool->setMaxThreadCount(QThread::idealThreadCount() + 1);
0269 }
0270 
0271 ThreadManager::~ThreadManager()
0272 {
0273     d->pool->waitForDone();
0274 
0275     delete d;
0276 }
0277 
0278 void ThreadManager::initialize(WorkerObject* const object)
0279 {
0280     connect(object, SIGNAL(destroyed(QObject*)),
0281             this, SLOT(slotDestroyed(QObject*)));
0282 
0283     d->changeMaxThreadCount(+1);
0284 
0285     d->parkingThread->parkObject(object);
0286 }
0287 
0288 void ThreadManager::initialize(DynamicThread* const dynamicThread)
0289 {
0290     connect(dynamicThread, SIGNAL(destroyed(QObject*)),
0291             this, SLOT(slotDestroyed(QObject*)));
0292 
0293     d->changeMaxThreadCount(+1);
0294 }
0295 
0296 void ThreadManager::schedule(WorkerObject* object)
0297 {
0298     d->pool->start(new WorkerObjectRunnable(object, d->parkingThread));
0299 }
0300 
0301 void ThreadManager::schedule(QRunnable* runnable)
0302 {
0303     d->pool->start(runnable);
0304 }
0305 
0306 void ThreadManager::slotDestroyed(QObject*)
0307 {
0308     d->changeMaxThreadCount(-1);
0309 }
0310 
0311 } // namespace Digikam
0312 
0313 #include "threadmanager.moc"
0314 
0315 #include "moc_threadmanager.cpp"