1 | // Copyright (C) 2016 The Qt Company Ltd. |
---|---|
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qthreadpool.h" |
5 | #include "qthreadpool_p.h" |
6 | #include "qdeadlinetimer.h" |
7 | #include "qcoreapplication.h" |
8 | |
9 | #include <QtCore/qpointer.h> |
10 | |
11 | #include <algorithm> |
12 | #include <climits> // For INT_MAX |
13 | #include <memory> |
14 | |
15 | using namespace std::chrono_literals; |
16 | |
17 | QT_BEGIN_NAMESPACE |
18 | |
19 | using namespace Qt::StringLiterals; |
20 | |
21 | /* |
22 | QThread wrapper, provides synchronization against a ThreadPool |
23 | */ |
24 | class QThreadPoolThread : public QThread |
25 | { |
26 | Q_OBJECT |
27 | public: |
28 | QThreadPoolThread(QThreadPoolPrivate *manager); |
29 | void run() override; |
30 | void registerThreadInactive(); |
31 | |
32 | QWaitCondition runnableReady; |
33 | QThreadPoolPrivate *manager; |
34 | QRunnable *runnable; |
35 | }; |
36 | |
37 | /* |
38 | QThreadPool private class. |
39 | */ |
40 | |
41 | |
42 | /*! |
43 | \internal |
44 | */ |
45 | QThreadPoolThread::QThreadPoolThread(QThreadPoolPrivate *manager) |
46 | :manager(manager), runnable(nullptr) |
47 | { |
48 | setStackSize(manager->stackSize); |
49 | } |
50 | |
51 | /* |
52 | \internal |
53 | */ |
54 | void QThreadPoolThread::run() |
55 | { |
56 | QMutexLocker locker(&manager->mutex); |
57 | for(;;) { |
58 | QRunnable *r = runnable; |
59 | runnable = nullptr; |
60 | |
61 | do { |
62 | if (r) { |
63 | // If autoDelete() is false, r might already be deleted after run(), so check status now. |
64 | const bool del = r->autoDelete(); |
65 | |
66 | // run the task |
67 | locker.unlock(); |
68 | #ifndef QT_NO_EXCEPTIONS |
69 | try { |
70 | #endif |
71 | r->run(); |
72 | #ifndef QT_NO_EXCEPTIONS |
73 | } catch (...) { |
74 | qWarning(msg: "Qt Concurrent has caught an exception thrown from a worker thread.\n" |
75 | "This is not supported, exceptions thrown in worker threads must be\n" |
76 | "caught before control returns to Qt Concurrent."); |
77 | registerThreadInactive(); |
78 | throw; |
79 | } |
80 | #endif |
81 | |
82 | if (del) |
83 | delete r; |
84 | locker.relock(); |
85 | } |
86 | |
87 | // if too many threads are active, stop working in this one |
88 | if (manager->tooManyThreadsActive()) |
89 | break; |
90 | |
91 | // all work is done, time to wait for more |
92 | if (manager->queue.isEmpty()) |
93 | break; |
94 | |
95 | QueuePage *page = manager->queue.constFirst(); |
96 | r = page->pop(); |
97 | |
98 | if (page->isFinished()) { |
99 | manager->queue.removeFirst(); |
100 | delete page; |
101 | } |
102 | } while (true); |
103 | |
104 | // this thread is about to be deleted, do not wait or expire |
105 | if (!manager->allThreads.contains(value: this)) { |
106 | registerThreadInactive(); |
107 | return; |
108 | } |
109 | |
110 | // if too many threads are active, expire this thread |
111 | if (manager->tooManyThreadsActive()) { |
112 | manager->expiredThreads.enqueue(t: this); |
113 | registerThreadInactive(); |
114 | return; |
115 | } |
116 | manager->waitingThreads.enqueue(t: this); |
117 | registerThreadInactive(); |
118 | // wait for work, exiting after the expiry timeout is reached |
119 | runnableReady.wait(lockedMutex: locker.mutex(), deadline: QDeadlineTimer(manager->expiryTimeout)); |
120 | // this thread is about to be deleted, do not work or expire |
121 | if (!manager->allThreads.contains(value: this)) { |
122 | Q_ASSERT(manager->queue.isEmpty()); |
123 | return; |
124 | } |
125 | if (manager->waitingThreads.removeOne(t: this)) { |
126 | manager->expiredThreads.enqueue(t: this); |
127 | return; |
128 | } |
129 | ++manager->activeThreads; |
130 | } |
131 | } |
132 | |
133 | void QThreadPoolThread::registerThreadInactive() |
134 | { |
135 | if (--manager->activeThreads == 0) |
136 | manager->noActiveThreads.wakeAll(); |
137 | } |
138 | |
139 | |
140 | /* |
141 | \internal |
142 | */ |
143 | QThreadPoolPrivate:: QThreadPoolPrivate() |
144 | { } |
145 | |
146 | bool QThreadPoolPrivate::tryStart(QRunnable *task) |
147 | { |
148 | Q_ASSERT(task != nullptr); |
149 | if (allThreads.isEmpty()) { |
150 | // always create at least one thread |
151 | startThread(runnable: task); |
152 | return true; |
153 | } |
154 | |
155 | // can't do anything if we're over the limit |
156 | if (areAllThreadsActive()) |
157 | return false; |
158 | |
159 | if (!waitingThreads.isEmpty()) { |
160 | // recycle an available thread |
161 | enqueueTask(task); |
162 | waitingThreads.takeFirst()->runnableReady.wakeOne(); |
163 | return true; |
164 | } |
165 | |
166 | if (!expiredThreads.isEmpty()) { |
167 | // restart an expired thread |
168 | QThreadPoolThread *thread = expiredThreads.dequeue(); |
169 | Q_ASSERT(thread->runnable == nullptr); |
170 | |
171 | ++activeThreads; |
172 | |
173 | thread->runnable = task; |
174 | |
175 | // Ensure that the thread has actually finished, otherwise the following |
176 | // start() has no effect. |
177 | thread->wait(); |
178 | Q_ASSERT(thread->isFinished()); |
179 | thread->start(threadPriority); |
180 | return true; |
181 | } |
182 | |
183 | // start a new thread |
184 | startThread(runnable: task); |
185 | return true; |
186 | } |
187 | |
188 | inline bool comparePriority(int priority, const QueuePage *p) |
189 | { |
190 | return p->priority() < priority; |
191 | } |
192 | |
193 | void QThreadPoolPrivate::enqueueTask(QRunnable *runnable, int priority) |
194 | { |
195 | Q_ASSERT(runnable != nullptr); |
196 | for (QueuePage *page : std::as_const(t&: queue)) { |
197 | if (page->priority() == priority && !page->isFull()) { |
198 | page->push(runnable); |
199 | return; |
200 | } |
201 | } |
202 | auto it = std::upper_bound(first: queue.constBegin(), last: queue.constEnd(), val: priority, comp: comparePriority); |
203 | queue.insert(i: std::distance(first: queue.constBegin(), last: it), t: new QueuePage(runnable, priority)); |
204 | } |
205 | |
206 | int QThreadPoolPrivate::activeThreadCount() const |
207 | { |
208 | return (allThreads.size() |
209 | - expiredThreads.size() |
210 | - waitingThreads.size() |
211 | + reservedThreads); |
212 | } |
213 | |
214 | void QThreadPoolPrivate::tryToStartMoreThreads() |
215 | { |
216 | // try to push tasks on the queue to any available threads |
217 | while (!queue.isEmpty()) { |
218 | QueuePage *page = queue.constFirst(); |
219 | if (!tryStart(task: page->first())) |
220 | break; |
221 | |
222 | page->pop(); |
223 | |
224 | if (page->isFinished()) { |
225 | queue.removeFirst(); |
226 | delete page; |
227 | } |
228 | } |
229 | } |
230 | |
231 | bool QThreadPoolPrivate::areAllThreadsActive() const |
232 | { |
233 | const int activeThreadCount = this->activeThreadCount(); |
234 | return activeThreadCount >= maxThreadCount() && (activeThreadCount - reservedThreads) >= 1; |
235 | } |
236 | |
237 | bool QThreadPoolPrivate::tooManyThreadsActive() const |
238 | { |
239 | const int activeThreadCount = this->activeThreadCount(); |
240 | return activeThreadCount > maxThreadCount() && (activeThreadCount - reservedThreads) > 1; |
241 | } |
242 | |
243 | /*! |
244 | \internal |
245 | */ |
246 | void QThreadPoolPrivate::startThread(QRunnable *runnable) |
247 | { |
248 | Q_ASSERT(runnable != nullptr); |
249 | auto thread = std::make_unique<QThreadPoolThread>(args: this); |
250 | if (objectName.isEmpty()) |
251 | objectName = u"Thread (pooled)"_s; |
252 | thread->setObjectName(objectName); |
253 | Q_ASSERT(!allThreads.contains(thread.get())); // if this assert hits, we have an ABA problem (deleted threads don't get removed here) |
254 | allThreads.insert(value: thread.get()); |
255 | ++activeThreads; |
256 | |
257 | thread->runnable = runnable; |
258 | thread.release()->start(threadPriority); |
259 | } |
260 | |
261 | /*! |
262 | \internal |
263 | |
264 | Helper function only to be called from waitForDone() |
265 | |
266 | Deletes all current threads. |
267 | */ |
268 | void QThreadPoolPrivate::reset() |
269 | { |
270 | // move the contents of the set out so that we can iterate without the lock |
271 | auto allThreadsCopy = std::exchange(obj&: allThreads, new_val: {}); |
272 | expiredThreads.clear(); |
273 | waitingThreads.clear(); |
274 | |
275 | mutex.unlock(); |
276 | |
277 | for (QThreadPoolThread *thread : std::as_const(t&: allThreadsCopy)) { |
278 | if (thread->isRunning()) { |
279 | thread->runnableReady.wakeAll(); |
280 | thread->wait(); |
281 | } |
282 | delete thread; |
283 | } |
284 | |
285 | mutex.lock(); |
286 | } |
287 | |
288 | /*! |
289 | \internal |
290 | |
291 | Helper function only to be called from the public waitForDone() |
292 | */ |
293 | bool QThreadPoolPrivate::waitForDone(const QDeadlineTimer &timer) |
294 | { |
295 | QMutexLocker locker(&mutex); |
296 | while (!(queue.isEmpty() && activeThreads == 0) && !timer.hasExpired()) |
297 | noActiveThreads.wait(lockedMutex: &mutex, deadline: timer); |
298 | |
299 | if (!queue.isEmpty() || activeThreads) |
300 | return false; |
301 | |
302 | reset(); |
303 | // New jobs might have started during reset, but return anyway |
304 | // as the active thread and task count did reach 0 once, and |
305 | // race conditions are outside our scope. |
306 | return true; |
307 | } |
308 | |
309 | void QThreadPoolPrivate::clear() |
310 | { |
311 | QMutexLocker locker(&mutex); |
312 | while (!queue.isEmpty()) { |
313 | auto *page = queue.takeLast(); |
314 | while (!page->isFinished()) { |
315 | QRunnable *r = page->pop(); |
316 | if (r && r->autoDelete()) { |
317 | locker.unlock(); |
318 | delete r; |
319 | locker.relock(); |
320 | } |
321 | } |
322 | delete page; |
323 | } |
324 | } |
325 | |
326 | /*! |
327 | \since 5.9 |
328 | |
329 | Attempts to remove the specified \a runnable from the queue if it is not yet started. |
330 | If the runnable had not been started, returns \c true, and ownership of \a runnable |
331 | is transferred to the caller (even when \c{runnable->autoDelete() == true}). |
332 | Otherwise returns \c false. |
333 | |
334 | \note If \c{runnable->autoDelete() == true}, this function may remove the wrong |
335 | runnable. This is known as the \l{https://en.wikipedia.org/wiki/ABA_problem}{ABA problem}: |
336 | the original \a runnable may already have executed and has since been deleted. |
337 | The memory is re-used for another runnable, which then gets removed instead of |
338 | the intended one. For this reason, we recommend calling this function only for |
339 | runnables that are not auto-deleting. |
340 | |
341 | \sa start(), QRunnable::autoDelete() |
342 | */ |
343 | bool QThreadPool::tryTake(QRunnable *runnable) |
344 | { |
345 | Q_D(QThreadPool); |
346 | |
347 | if (runnable == nullptr) |
348 | return false; |
349 | |
350 | QMutexLocker locker(&d->mutex); |
351 | for (QueuePage *page : std::as_const(t&: d->queue)) { |
352 | if (page->tryTake(runnable)) { |
353 | if (page->isFinished()) { |
354 | d->queue.removeOne(t: page); |
355 | delete page; |
356 | } |
357 | return true; |
358 | } |
359 | } |
360 | |
361 | return false; |
362 | } |
363 | |
364 | /*! |
365 | \internal |
366 | Searches for \a runnable in the queue, removes it from the queue and |
367 | runs it if found. This function does not return until the runnable |
368 | has completed. |
369 | */ |
370 | void QThreadPoolPrivate::stealAndRunRunnable(QRunnable *runnable) |
371 | { |
372 | Q_Q(QThreadPool); |
373 | if (!q->tryTake(runnable)) |
374 | return; |
375 | // If autoDelete() is false, runnable might already be deleted after run(), so check status now. |
376 | const bool del = runnable->autoDelete(); |
377 | |
378 | runnable->run(); |
379 | |
380 | if (del) |
381 | delete runnable; |
382 | } |
383 | |
384 | /*! |
385 | \class QThreadPool |
386 | \inmodule QtCore |
387 | \brief The QThreadPool class manages a collection of QThreads. |
388 | \since 4.4 |
389 | \threadsafe |
390 | |
391 | \ingroup thread |
392 | |
393 | QThreadPool manages and recycles individual QThread objects to help reduce |
394 | thread creation costs in programs that use threads. Each Qt application |
395 | has one global QThreadPool object, which can be accessed by calling |
396 | globalInstance(). |
397 | |
398 | To use one of the QThreadPool threads, subclass QRunnable and implement |
399 | the run() virtual function. Then create an object of that class and pass |
400 | it to QThreadPool::start(). |
401 | |
402 | \snippet code/src_corelib_concurrent_qthreadpool.cpp 0 |
403 | |
404 | QThreadPool deletes the QRunnable automatically by default. Use |
405 | QRunnable::setAutoDelete() to change the auto-deletion flag. |
406 | |
407 | QThreadPool supports executing the same QRunnable more than once |
408 | by calling tryStart(this) from within QRunnable::run(). |
409 | If autoDelete is enabled the QRunnable will be deleted when |
410 | the last thread exits the run function. Calling start() |
411 | multiple times with the same QRunnable when autoDelete is enabled |
412 | creates a race condition and is not recommended. |
413 | |
414 | Threads that are unused for a certain amount of time will expire. The |
415 | default expiry timeout is 30000 milliseconds (30 seconds). This can be |
416 | changed using setExpiryTimeout(). Setting a negative expiry timeout |
417 | disables the expiry mechanism. |
418 | |
419 | Call maxThreadCount() to query the maximum number of threads to be used. |
420 | If needed, you can change the limit with setMaxThreadCount(). The default |
421 | maxThreadCount() is QThread::idealThreadCount(). The activeThreadCount() |
422 | function returns the number of threads currently doing work. |
423 | |
424 | The reserveThread() function reserves a thread for external |
425 | use. Use releaseThread() when your are done with the thread, so |
426 | that it may be reused. Essentially, these functions temporarily |
427 | increase or reduce the active thread count and are useful when |
428 | implementing time-consuming operations that are not visible to the |
429 | QThreadPool. |
430 | |
431 | Note that QThreadPool is a low-level class for managing threads, see |
432 | the Qt Concurrent module for higher level alternatives. |
433 | |
434 | \sa QRunnable |
435 | */ |
436 | |
437 | /*! |
438 | Constructs a thread pool with the given \a parent. |
439 | */ |
440 | QThreadPool::QThreadPool(QObject *parent) |
441 | : QObject(*new QThreadPoolPrivate, parent) |
442 | { |
443 | Q_D(QThreadPool); |
444 | connect(sender: this, signal: &QObject::objectNameChanged, context: this, slot: [d](const QString &newName) { |
445 | // We keep a copy of the name under our own lock, so we can access it thread-safely. |
446 | QMutexLocker locker(&d->mutex); |
447 | d->objectName = newName; |
448 | }); |
449 | } |
450 | |
451 | /*! |
452 | Destroys the QThreadPool. |
453 | This function will block until all runnables have been completed. |
454 | */ |
455 | QThreadPool::~QThreadPool() |
456 | { |
457 | Q_D(QThreadPool); |
458 | waitForDone(); |
459 | Q_ASSERT(d->queue.isEmpty()); |
460 | Q_ASSERT(d->allThreads.isEmpty()); |
461 | } |
462 | |
463 | /*! |
464 | Returns the global QThreadPool instance. |
465 | */ |
466 | QThreadPool *QThreadPool::globalInstance() |
467 | { |
468 | Q_CONSTINIT static QPointer<QThreadPool> theInstance; |
469 | Q_CONSTINIT static QBasicMutex theMutex; |
470 | |
471 | const QMutexLocker locker(&theMutex); |
472 | if (theInstance.isNull() && !QCoreApplication::closingDown()) |
473 | theInstance = new QThreadPool(); |
474 | return theInstance; |
475 | } |
476 | |
477 | /*! |
478 | Returns the QThreadPool instance for Qt Gui. |
479 | \internal |
480 | */ |
481 | QThreadPool *QThreadPoolPrivate::qtGuiInstance() |
482 | { |
483 | Q_CONSTINIT static QPointer<QThreadPool> guiInstance; |
484 | Q_CONSTINIT static QBasicMutex theMutex; |
485 | const static bool runtime_disable = qEnvironmentVariableIsSet(varName: "QT_NO_GUI_THREADPOOL"); |
486 | if (runtime_disable) |
487 | return nullptr; |
488 | const QMutexLocker locker(&theMutex); |
489 | if (guiInstance.isNull() && !QCoreApplication::closingDown()) { |
490 | guiInstance = new QThreadPool(); |
491 | // Limit max thread to avoid too many parallel threads. |
492 | // We are not optimized for much more than 4 or 8 threads. |
493 | if (guiInstance && guiInstance->maxThreadCount() > 4) |
494 | guiInstance->setMaxThreadCount(qBound(min: 4, val: guiInstance->maxThreadCount() / 2, max: 8)); |
495 | } |
496 | return guiInstance; |
497 | } |
498 | |
499 | /*! |
500 | Reserves a thread and uses it to run \a runnable, unless this thread will |
501 | make the current thread count exceed maxThreadCount(). In that case, |
502 | \a runnable is added to a run queue instead. The \a priority argument can |
503 | be used to control the run queue's order of execution. |
504 | |
505 | Note that the thread pool takes ownership of the \a runnable if |
506 | \l{QRunnable::autoDelete()}{runnable->autoDelete()} returns \c true, |
507 | and the \a runnable will be deleted automatically by the thread |
508 | pool after the \l{QRunnable::run()}{runnable->run()} returns. If |
509 | \l{QRunnable::autoDelete()}{runnable->autoDelete()} returns \c false, |
510 | ownership of \a runnable remains with the caller. Note that |
511 | changing the auto-deletion on \a runnable after calling this |
512 | functions results in undefined behavior. |
513 | */ |
514 | void QThreadPool::start(QRunnable *runnable, int priority) |
515 | { |
516 | if (!runnable) |
517 | return; |
518 | |
519 | Q_D(QThreadPool); |
520 | QMutexLocker locker(&d->mutex); |
521 | |
522 | if (!d->tryStart(task: runnable)) |
523 | d->enqueueTask(runnable, priority); |
524 | } |
525 | |
526 | /*! |
527 | \fn template<typename Callable, QRunnable::if_callable<Callable>> void QThreadPool::start(Callable &&callableToRun, int priority) |
528 | \overload |
529 | \since 5.15 |
530 | |
531 | Reserves a thread and uses it to run \a callableToRun, unless this thread will |
532 | make the current thread count exceed maxThreadCount(). In that case, |
533 | \a callableToRun is added to a run queue instead. The \a priority argument can |
534 | be used to control the run queue's order of execution. |
535 | |
536 | \note This function participates in overload resolution only if \c Callable |
537 | is a function or function object which can be called with zero arguments. |
538 | |
539 | \note In Qt version prior to 6.6, this function took std::function<void()>, |
540 | and therefore couldn't handle move-only callables. |
541 | */ |
542 | |
543 | /*! |
544 | Attempts to reserve a thread to run \a runnable. |
545 | |
546 | If no threads are available at the time of calling, then this function |
547 | does nothing and returns \c false. Otherwise, \a runnable is run immediately |
548 | using one available thread and this function returns \c true. |
549 | |
550 | Note that on success the thread pool takes ownership of the \a runnable if |
551 | \l{QRunnable::autoDelete()}{runnable->autoDelete()} returns \c true, |
552 | and the \a runnable will be deleted automatically by the thread |
553 | pool after the \l{QRunnable::run()}{runnable->run()} returns. If |
554 | \l{QRunnable::autoDelete()}{runnable->autoDelete()} returns \c false, |
555 | ownership of \a runnable remains with the caller. Note that |
556 | changing the auto-deletion on \a runnable after calling this |
557 | function results in undefined behavior. |
558 | */ |
559 | bool QThreadPool::tryStart(QRunnable *runnable) |
560 | { |
561 | if (!runnable) |
562 | return false; |
563 | |
564 | Q_D(QThreadPool); |
565 | QMutexLocker locker(&d->mutex); |
566 | if (d->tryStart(task: runnable)) |
567 | return true; |
568 | |
569 | return false; |
570 | } |
571 | |
572 | /*! |
573 | \fn template<typename Callable, QRunnable::if_callable<Callable>> bool QThreadPool::tryStart(Callable &&callableToRun) |
574 | \overload |
575 | \since 5.15 |
576 | Attempts to reserve a thread to run \a callableToRun. |
577 | |
578 | If no threads are available at the time of calling, then this function |
579 | does nothing and returns \c false. Otherwise, \a callableToRun is run immediately |
580 | using one available thread and this function returns \c true. |
581 | |
582 | \note This function participates in overload resolution only if \c Callable |
583 | is a function or function object which can be called with zero arguments. |
584 | |
585 | \note In Qt version prior to 6.6, this function took std::function<void()>, |
586 | and therefore couldn't handle move-only callables. |
587 | */ |
588 | |
589 | /*! \property QThreadPool::expiryTimeout |
590 | \brief the thread expiry timeout value in milliseconds. |
591 | |
592 | Threads that are unused for \e expiryTimeout milliseconds are considered |
593 | to have expired and will exit. Such threads will be restarted as needed. |
594 | The default \a expiryTimeout is 30000 milliseconds (30 seconds). If |
595 | \a expiryTimeout is negative, newly created threads will not expire, e.g., |
596 | they will not exit until the thread pool is destroyed. |
597 | |
598 | Note that setting \a expiryTimeout has no effect on already running |
599 | threads. Only newly created threads will use the new \a expiryTimeout. |
600 | We recommend setting the \a expiryTimeout immediately after creating the |
601 | thread pool, but before calling start(). |
602 | */ |
603 | |
604 | int QThreadPool::expiryTimeout() const |
605 | { |
606 | using namespace std::chrono; |
607 | Q_D(const QThreadPool); |
608 | QMutexLocker locker(&d->mutex); |
609 | if (d->expiryTimeout == decltype(d->expiryTimeout)::max()) |
610 | return -1; |
611 | return duration_cast<milliseconds>(d: d->expiryTimeout).count(); |
612 | } |
613 | |
614 | void QThreadPool::setExpiryTimeout(int expiryTimeout) |
615 | { |
616 | Q_D(QThreadPool); |
617 | QMutexLocker locker(&d->mutex); |
618 | if (expiryTimeout < 0) |
619 | d->expiryTimeout = decltype(d->expiryTimeout)::max(); |
620 | else |
621 | d->expiryTimeout = expiryTimeout * 1ms; |
622 | } |
623 | |
624 | /*! \property QThreadPool::maxThreadCount |
625 | |
626 | \brief the maximum number of threads used by the thread pool. This property |
627 | will default to the value of QThread::idealThreadCount() at the moment the |
628 | QThreadPool object is created. |
629 | |
630 | \note The thread pool will always use at least 1 thread, even if |
631 | \a maxThreadCount limit is zero or negative. |
632 | |
633 | The default \a maxThreadCount is QThread::idealThreadCount(). |
634 | */ |
635 | |
636 | int QThreadPool::maxThreadCount() const |
637 | { |
638 | Q_D(const QThreadPool); |
639 | QMutexLocker locker(&d->mutex); |
640 | return d->requestedMaxThreadCount; |
641 | } |
642 | |
643 | void QThreadPool::setMaxThreadCount(int maxThreadCount) |
644 | { |
645 | Q_D(QThreadPool); |
646 | QMutexLocker locker(&d->mutex); |
647 | |
648 | if (maxThreadCount == d->requestedMaxThreadCount) |
649 | return; |
650 | |
651 | d->requestedMaxThreadCount = maxThreadCount; |
652 | d->tryToStartMoreThreads(); |
653 | } |
654 | |
655 | /*! \property QThreadPool::activeThreadCount |
656 | |
657 | \brief the number of active threads in the thread pool. |
658 | |
659 | \note It is possible for this function to return a value that is greater |
660 | than maxThreadCount(). See reserveThread() for more details. |
661 | |
662 | \sa reserveThread(), releaseThread() |
663 | */ |
664 | |
665 | int QThreadPool::activeThreadCount() const |
666 | { |
667 | Q_D(const QThreadPool); |
668 | QMutexLocker locker(&d->mutex); |
669 | return d->activeThreadCount(); |
670 | } |
671 | |
672 | /*! |
673 | Reserves one thread, disregarding activeThreadCount() and maxThreadCount(). |
674 | |
675 | Once you are done with the thread, call releaseThread() to allow it to be |
676 | reused. |
677 | |
678 | \note Even if reserving maxThreadCount() threads or more, the thread pool |
679 | will still allow a minimum of one thread. |
680 | |
681 | \note This function will increase the reported number of active threads. |
682 | This means that by using this function, it is possible for |
683 | activeThreadCount() to return a value greater than maxThreadCount() . |
684 | |
685 | \sa releaseThread() |
686 | */ |
687 | void QThreadPool::reserveThread() |
688 | { |
689 | Q_D(QThreadPool); |
690 | QMutexLocker locker(&d->mutex); |
691 | ++d->reservedThreads; |
692 | } |
693 | |
694 | /*! \property QThreadPool::stackSize |
695 | \brief the stack size for the thread pool worker threads. |
696 | |
697 | The value of the property is only used when the thread pool creates |
698 | new threads. Changing it has no effect for already created |
699 | or running threads. |
700 | |
701 | The default value is 0, which makes QThread use the operating |
702 | system default stack size. |
703 | |
704 | \since 5.10 |
705 | */ |
706 | void QThreadPool::setStackSize(uint stackSize) |
707 | { |
708 | Q_D(QThreadPool); |
709 | QMutexLocker locker(&d->mutex); |
710 | d->stackSize = stackSize; |
711 | } |
712 | |
713 | uint QThreadPool::stackSize() const |
714 | { |
715 | Q_D(const QThreadPool); |
716 | QMutexLocker locker(&d->mutex); |
717 | return d->stackSize; |
718 | } |
719 | |
720 | /*! \property QThreadPool::threadPriority |
721 | \brief the thread priority for new worker threads. |
722 | |
723 | The value of the property is only used when the thread pool starts |
724 | new threads. Changing it has no effect for already running threads. |
725 | |
726 | The default value is QThread::InheritPriority, which makes QThread |
727 | use the same priority as the one the QThreadPool object lives in. |
728 | |
729 | \sa QThread::Priority |
730 | |
731 | \since 6.2 |
732 | */ |
733 | |
734 | void QThreadPool::setThreadPriority(QThread::Priority priority) |
735 | { |
736 | Q_D(QThreadPool); |
737 | QMutexLocker locker(&d->mutex); |
738 | d->threadPriority = priority; |
739 | } |
740 | |
741 | QThread::Priority QThreadPool::threadPriority() const |
742 | { |
743 | Q_D(const QThreadPool); |
744 | QMutexLocker locker(&d->mutex); |
745 | return d->threadPriority; |
746 | } |
747 | |
748 | /*! |
749 | Releases a thread previously reserved by a call to reserveThread(). |
750 | |
751 | \note Calling this function without previously reserving a thread |
752 | temporarily increases maxThreadCount(). This is useful when a |
753 | thread goes to sleep waiting for more work, allowing other threads |
754 | to continue. Be sure to call reserveThread() when done waiting, so |
755 | that the thread pool can correctly maintain the |
756 | activeThreadCount(). |
757 | |
758 | \sa reserveThread() |
759 | */ |
760 | void QThreadPool::releaseThread() |
761 | { |
762 | Q_D(QThreadPool); |
763 | QMutexLocker locker(&d->mutex); |
764 | --d->reservedThreads; |
765 | d->tryToStartMoreThreads(); |
766 | } |
767 | |
768 | /*! |
769 | Releases a thread previously reserved with reserveThread() and uses it |
770 | to run \a runnable. |
771 | |
772 | Note that the thread pool takes ownership of the \a runnable if |
773 | \l{QRunnable::autoDelete()}{runnable->autoDelete()} returns \c true, |
774 | and the \a runnable will be deleted automatically by the thread |
775 | pool after the \l{QRunnable::run()}{runnable->run()} returns. If |
776 | \l{QRunnable::autoDelete()}{runnable->autoDelete()} returns \c false, |
777 | ownership of \a runnable remains with the caller. Note that |
778 | changing the auto-deletion on \a runnable after calling this |
779 | functions results in undefined behavior. |
780 | |
781 | \note Calling this when no threads are reserved results in |
782 | undefined behavior. |
783 | |
784 | \since 6.3 |
785 | \sa reserveThread(), start() |
786 | */ |
787 | void QThreadPool::startOnReservedThread(QRunnable *runnable) |
788 | { |
789 | if (!runnable) |
790 | return releaseThread(); |
791 | |
792 | Q_D(QThreadPool); |
793 | QMutexLocker locker(&d->mutex); |
794 | Q_ASSERT(d->reservedThreads > 0); |
795 | --d->reservedThreads; |
796 | |
797 | if (!d->tryStart(task: runnable)) { |
798 | // This can only happen if we reserved max threads, |
799 | // and something took the one minimum thread. |
800 | d->enqueueTask(runnable, INT_MAX); |
801 | } |
802 | } |
803 | |
804 | /*! |
805 | \fn template<typename Callable, QRunnable::if_callable<Callable>> void QThreadPool::startOnReservedThread(Callable &&callableToRun) |
806 | \overload |
807 | \since 6.3 |
808 | |
809 | Releases a thread previously reserved with reserveThread() and uses it |
810 | to run \a callableToRun. |
811 | |
812 | \note This function participates in overload resolution only if \c Callable |
813 | is a function or function object which can be called with zero arguments. |
814 | |
815 | \note In Qt version prior to 6.6, this function took std::function<void()>, |
816 | and therefore couldn't handle move-only callables. |
817 | */ |
818 | |
819 | /*! |
820 | \fn bool QThreadPool::waitForDone(int msecs) |
821 | Waits up to \a msecs milliseconds for all threads to exit and removes all |
822 | threads from the thread pool. Returns \c true if all threads were removed; |
823 | otherwise it returns \c false. If \a msecs is -1, this function waits for |
824 | the last thread to exit. |
825 | */ |
826 | |
827 | /*! |
828 | \since 6.8 |
829 | |
830 | Waits until \a deadline expires for all threads to exit and removes all |
831 | threads from the thread pool. Returns \c true if all threads were removed; |
832 | otherwise it returns \c false. |
833 | */ |
834 | bool QThreadPool::waitForDone(QDeadlineTimer deadline) |
835 | { |
836 | Q_D(QThreadPool); |
837 | return d->waitForDone(timer: deadline); |
838 | } |
839 | |
840 | /*! |
841 | \since 5.2 |
842 | |
843 | Removes the runnables that are not yet started from the queue. |
844 | The runnables for which \l{QRunnable::autoDelete()}{runnable->autoDelete()} |
845 | returns \c true are deleted. |
846 | |
847 | \sa start() |
848 | */ |
849 | void QThreadPool::clear() |
850 | { |
851 | Q_D(QThreadPool); |
852 | d->clear(); |
853 | } |
854 | |
855 | /*! |
856 | \since 6.0 |
857 | |
858 | Returns \c true if \a thread is a thread managed by this thread pool. |
859 | */ |
860 | bool QThreadPool::contains(const QThread *thread) const |
861 | { |
862 | Q_D(const QThreadPool); |
863 | const QThreadPoolThread *poolThread = qobject_cast<const QThreadPoolThread *>(object: thread); |
864 | if (!poolThread) |
865 | return false; |
866 | QMutexLocker locker(&d->mutex); |
867 | return d->allThreads.contains(value: const_cast<QThreadPoolThread *>(poolThread)); |
868 | } |
869 | |
870 | QT_END_NAMESPACE |
871 | |
872 | #include "moc_qthreadpool.cpp" |
873 | #include "qthreadpool.moc" |
874 |
Definitions
- QThreadPoolThread
- QThreadPoolThread
- run
- registerThreadInactive
- QThreadPoolPrivate
- tryStart
- comparePriority
- enqueueTask
- activeThreadCount
- tryToStartMoreThreads
- areAllThreadsActive
- tooManyThreadsActive
- startThread
- reset
- waitForDone
- clear
- tryTake
- stealAndRunRunnable
- QThreadPool
- ~QThreadPool
- globalInstance
- qtGuiInstance
- start
- tryStart
- expiryTimeout
- setExpiryTimeout
- maxThreadCount
- setMaxThreadCount
- activeThreadCount
- reserveThread
- setStackSize
- stackSize
- setThreadPriority
- threadPriority
- releaseThread
- startOnReservedThread
- waitForDone
- clear
Learn Advanced QML with KDAB
Find out more