diff --git a/src/corelib/thread/qthread.cpp b/src/corelib/thread/qthread.cpp index 9d42c5dc02a..4fb9caae1f0 100644 --- a/src/corelib/thread/qthread.cpp +++ b/src/corelib/thread/qthread.cpp @@ -560,6 +560,46 @@ uint QThread::stackSize() const return d->stackSize; } +/*! + \since 6.9 + + Set the Quality of Service level of the thread object to \a serviceLevel. + This can only be called from the thread itself or before the thread is + started! + + This is currently only implemented on Apple platforms, and Windows. + The function call will complete successfully on other platforms but will + not currently have any effect. + + \sa serviceLevel() +*/ +void QThread::setServiceLevel(QualityOfService serviceLevel) +{ + Q_D(QThread); + QMutexLocker locker(&d->mutex); + if (d->threadState != QThreadPrivate::Running) { + d->serviceLevel = serviceLevel; + } else { + Q_ASSERT_X(isCurrentThread(), "QThread::setServiceLevel", + "cannot change quality of service level of a separate, running, thread"); + d->setQualityOfServiceLevel(serviceLevel); + } +} + +/*! + \since 6.9 + + Return the current Quality of Service level of the thread. + + \sa setServiceLevel() +*/ +QThread::QualityOfService QThread::serviceLevel() const +{ + Q_D(const QThread); + QMutexLocker locker(&d->mutex); + return d->serviceLevel; +} + /*! \internal Transitions BindingStatusOrList to the binding status state. If we had a list of diff --git a/src/corelib/thread/qthread.h b/src/corelib/thread/qthread.h index ae060ecf1cf..eea2354ef9e 100644 --- a/src/corelib/thread/qthread.h +++ b/src/corelib/thread/qthread.h @@ -51,6 +51,13 @@ public: InheritPriority }; + enum class QualityOfService { + Auto, + High, + Eco, + }; + Q_ENUM(QualityOfService) + void setPriority(Priority priority); Priority priority() const; @@ -71,6 +78,9 @@ public: bool isCurrentThread() const noexcept; + void setServiceLevel(QualityOfService serviceLevel); + QualityOfService serviceLevel() const; + template [[nodiscard]] static QThread *create(Function &&f, Args &&... args); diff --git a/src/corelib/thread/qthread_p.h b/src/corelib/thread/qthread_p.h index ada010dfefe..f7ee8445711 100644 --- a/src/corelib/thread/qthread_p.h +++ b/src/corelib/thread/qthread_p.h @@ -206,6 +206,12 @@ public: bool wait(QMutexLocker &locker, QDeadlineTimer deadline); + QThread::QualityOfService serviceLevel = QThread::QualityOfService::Auto; + void setQualityOfServiceLevel(QThread::QualityOfService qosLevel); +#ifdef Q_OS_DARWIN + qos_class_t nativeQualityOfServiceClass() const; +#endif + #ifdef Q_OS_UNIX QWaitCondition thread_done; diff --git a/src/corelib/thread/qthread_unix.cpp b/src/corelib/thread/qthread_unix.cpp index c2b8863f044..301543efa03 100644 --- a/src/corelib/thread/qthread_unix.cpp +++ b/src/corelib/thread/qthread_unix.cpp @@ -62,6 +62,9 @@ QT_BEGIN_NAMESPACE +[[maybe_unused]] +Q_STATIC_LOGGING_CATEGORY(lcQThread, "qt.core.thread", QtWarningMsg) + using namespace QtMiscUtils; #if QT_CONFIG(thread) @@ -315,6 +318,10 @@ void *QThreadPrivate::start(void *arg) if (thr->d_func()->priority & ThreadPriorityResetFlag) { thr->d_func()->setPriority(QThread::Priority(thr->d_func()->priority & ~ThreadPriorityResetFlag)); } +#ifndef Q_OS_DARWIN // For Darwin we set it as an attribute when starting the thread + if (thr->d_func()->serviceLevel != QThread::QualityOfService::Auto) + thr->d_func()->setQualityOfServiceLevel(thr->d_func()->serviceLevel); +#endif // threadId is set in QThread::start() Q_ASSERT(data->threadId.loadRelaxed() == QThread::currentThreadId()); @@ -680,6 +687,10 @@ void QThread::start(Priority priority) pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); +#ifdef Q_OS_DARWIN + if (d->serviceLevel != QThread::QualityOfService::Auto) + pthread_attr_set_qos_class_np(&attr, d->nativeQualityOfServiceClass(), 0); +#endif d->priority = priority; @@ -901,6 +912,39 @@ void QThreadPrivate::setPriority(QThread::Priority threadPriority) #endif } +void QThreadPrivate::setQualityOfServiceLevel(QThread::QualityOfService qosLevel) +{ + [[maybe_unused]] + Q_Q(QThread); + serviceLevel = qosLevel; +#ifdef Q_OS_DARWIN + qCDebug(lcQThread) << "Setting thread QoS class to" << serviceLevel << "for thread" << q; + pthread_set_qos_class_self_np(nativeQualityOfServiceClass(), 0); +#endif +} + +#ifdef Q_OS_DARWIN +qos_class_t QThreadPrivate::nativeQualityOfServiceClass() const +{ + // @note Consult table[0] to see what the levels mean + // [0] https://developer.apple.com/library/archive/documentation/Performance/Conceptual/power_efficiency_guidelines_osx/PrioritizeWorkAtTheTaskLevel.html#//apple_ref/doc/uid/TP40013929-CH35-SW5 + // There are more levels but they have two other documented ones, + // QOS_CLASS_BACKGROUND, which is below UTILITY, but has no guarantees + // for scheduling (ie. the OS could choose to never give it CPU time), + // and QOS_CLASS_USER_INITIATED, documented as being intended for + // user-initiated actions, such as loading a text document. + switch (serviceLevel) { + case QThread::QualityOfService::Auto: + return QOS_CLASS_DEFAULT; + case QThread::QualityOfService::High: + return QOS_CLASS_USER_INTERACTIVE; + case QThread::QualityOfService::Eco: + return QOS_CLASS_UTILITY; + } + Q_UNREACHABLE_RETURN(QOS_CLASS_DEFAULT); +} +#endif + #endif // QT_CONFIG(thread) QT_END_NAMESPACE diff --git a/src/corelib/thread/qthread_win.cpp b/src/corelib/thread/qthread_win.cpp index 3fb07db62da..f56da79d78d 100644 --- a/src/corelib/thread/qthread_win.cpp +++ b/src/corelib/thread/qthread_win.cpp @@ -28,8 +28,22 @@ SetThreadDescription( ); } +#ifndef THREAD_POWER_THROTTLING_EXECUTION_SPEED +#define THREAD_POWER_THROTTLING_EXECUTION_SPEED 0x1 +#define THREAD_POWER_THROTTLING_CURRENT_VERSION 1 + +typedef struct _THREAD_POWER_THROTTLING_STATE { + ULONG Version; + ULONG ControlMask; + ULONG StateMask; +} THREAD_POWER_THROTTLING_STATE; +#endif + + QT_BEGIN_NAMESPACE +Q_STATIC_LOGGING_CATEGORY(lcQThread, "qt.core.thread", QtWarningMsg) + #if QT_CONFIG(thread) void qt_watch_adopted_thread(const HANDLE adoptedThreadHandle, QThread *qthread); @@ -251,6 +265,9 @@ unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(voi { QMutexLocker locker(&thr->d_func()->mutex); data->quitNow = thr->d_func()->exited; + + if (thr->d_func()->serviceLevel != QThread::QualityOfService::Auto) + thr->d_func()->setQualityOfServiceLevel(thr->d_func()->serviceLevel); } data->ensureEventDispatcher(); @@ -270,6 +287,39 @@ unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(voi return 0; } +void QThreadPrivate::setQualityOfServiceLevel(QThread::QualityOfService qosLevel) +{ + Q_Q(QThread); + serviceLevel = qosLevel; + +#if (_WIN32_WINNT >= _WIN32_WINNT_WIN10_RS3) + qCDebug(lcQThread) << "Setting thread QoS class to" << qosLevel << "for thread" << q; + + THREAD_POWER_THROTTLING_STATE state; + memset(&state, 0, sizeof(state)); + state.Version = THREAD_POWER_THROTTLING_CURRENT_VERSION; + + switch (qosLevel) { + case QThread::QualityOfService::Auto: + state.ControlMask = 0; // Unset control of QoS + state.StateMask = 0; + break; + case QThread::QualityOfService::Eco: + state.ControlMask = THREAD_POWER_THROTTLING_EXECUTION_SPEED; + state.StateMask = THREAD_POWER_THROTTLING_EXECUTION_SPEED; + break; + case QThread::QualityOfService::High: + state.ControlMask = THREAD_POWER_THROTTLING_EXECUTION_SPEED; + state.StateMask = 0; // Ask to disable throttling + break; + } + if (!SetThreadInformation(::GetCurrentThread(), THREAD_INFORMATION_CLASS::ThreadPowerThrottling, + &state, sizeof(state))) { + qErrnoWarning("Failed to set thread power throttling state"); + } +#endif +} + /* For regularly terminating threads, this will be called and executed by the thread as the last code before the thread exits. In that case, \a arg is the current QThread. diff --git a/tests/auto/corelib/thread/qthread/tst_qthread.cpp b/tests/auto/corelib/thread/qthread/tst_qthread.cpp index 696b649dc76..99602098fac 100644 --- a/tests/auto/corelib/thread/qthread/tst_qthread.cpp +++ b/tests/auto/corelib/thread/qthread/tst_qthread.cpp @@ -105,6 +105,8 @@ private slots: void terminateSelfStressTest(); void bindingListCleanupAfterDelete(); + + void qualityOfService(); }; enum { one_minute = 60 * 1000, five_minutes = 5 * one_minute }; @@ -2104,5 +2106,33 @@ void tst_QThread::bindingListCleanupAfterDelete() QVERIFY(list->empty()); } +void tst_QThread::qualityOfService() +{ + QThread th; + QThread::currentThread()->setObjectName("Main thread"); + th.setObjectName("test thread"); + auto guard = qScopeGuard([&th](){ th.quit(); th.wait(); }); + QCOMPARE(th.serviceLevel(), QThread::QualityOfService::Auto); + th.setServiceLevel(QThread::QualityOfService::High); + QCOMPARE(th.serviceLevel(), QThread::QualityOfService::High); + th.setServiceLevel(QThread::QualityOfService::Eco); + QCOMPARE(th.serviceLevel(), QThread::QualityOfService::Eco); + + th.start(); + auto obj = std::make_unique(); + obj->moveToThread(&th); + + QThread::QualityOfService launchedThreadServiceLevel = {}; + QMetaObject::invokeMethod(obj.get(), [](){ + return QThread::currentThread()->serviceLevel(); + }, Qt::BlockingQueuedConnection, qReturnArg(launchedThreadServiceLevel)); + + QCOMPARE(launchedThreadServiceLevel, QThread::QualityOfService::Eco); + + QMetaObject::invokeMethod(obj.get(), [](){ + QThread::currentThread()->setServiceLevel(QThread::QualityOfService::High); + }, Qt::BlockingQueuedConnection); +} + QTEST_MAIN(tst_QThread) #include "tst_qthread.moc"