MythTV master
mthreadpool.cpp
Go to the documentation of this file.
1/* -*- Mode: c++ -*-
2 *
3 * Class MThreadPool
4 *
5 * Copyright (C) Daniel Kristjansson 2011
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
75// C++ headers
76#include <algorithm>
77
78// Qt headers
79#include <QCoreApplication>
80#include <QList>
81#include <QMap>
82#include <QMutex>
83#include <QMutexLocker>
84#include <QPair>
85#include <QRunnable>
86#include <QRecursiveMutex>
87#include <QSet>
88#include <QWaitCondition>
89#include <utility>
90
91// MythTV headers
92#include "mthreadpool.h"
93#include "mythlogging.h"
94#include "mythtimer.h"
95#include "logging.h"
96#include "mthread.h"
97#include "mythdb.h"
98
99using MPoolEntry = QPair<QRunnable*,QString>;
100using MPoolQueue = QList<MPoolEntry>;
101using MPoolQueues = QMap<int, MPoolQueue>;
102
103class MPoolThread : public MThread
104{
105 public:
106 MPoolThread(const QString& objectName, MThreadPool &pool, std::chrono::milliseconds timeout) :
108 {
109 }
110
111 protected:
112 void run(void) override // MThread
113 {
114 RunProlog();
115
116 QMutexLocker locker(&m_lock);
117 if (m_doRun && m_runnable == nullptr)
118 {
119 m_wait.wait(locker.mutex(), (m_expiryTimeout+1ms).count());
120 }
121 while (m_runnable != nullptr)
122 {
123 if (!m_runnableName.isEmpty())
125
126 bool autodelete = m_runnable->autoDelete();
127 locker.unlock();
128 m_runnable->run();
129 locker.relock();
130 if (autodelete)
131 delete m_runnable;
132 if (m_reserved)
133 {
134 locker.unlock();
136 locker.relock();
137 }
138 m_reserved = false;
139 m_runnable = nullptr;
140
143
144 GetMythDB()->GetDBManager()->PurgeIdleConnections(false);
145 qApp->processEvents();
146 qApp->sendPostedEvents(nullptr, QEvent::DeferredDelete);
147
148 if (m_doRun)
149 {
150 locker.unlock();
152 locker.relock();
153 }
154 if (m_doRun && m_runnable == nullptr)
155 {
156 m_wait.wait(locker.mutex(), (m_expiryTimeout+1ms).count());
157 }
158 }
159
160 m_doRun = false;
161
162 locker.unlock();
163 m_pool.NotifyDone(this);
164 locker.relock();
165
166 RunEpilog();
167 }
168
169 public:
170 bool SetRunnable(QRunnable *runnable, QString runnableName,
171 bool reserved)
172 {
173 QMutexLocker locker(&m_lock);
174 if (m_doRun && (m_runnable == nullptr))
175 {
176 m_runnable = runnable;
177 m_runnableName = std::move(runnableName);
178 m_reserved = reserved;
179 m_wait.wakeAll();
180 return true;
181 }
182 return false;
183 }
184
185 void Shutdown(void)
186 {
187 QMutexLocker locker(&m_lock);
188 m_doRun = false;
189 m_wait.wakeAll();
190 }
191
192 QMutex m_lock;
193 QWaitCondition m_wait;
195 std::chrono::milliseconds m_expiryTimeout;
196 bool m_doRun {true};
198 bool m_reserved {false};
199};
200
202
204{
205 public:
206 explicit MThreadPoolPrivate(QString name) :
207 m_name(std::move(name)) {}
208
209 int GetRealMaxThread(void) const
210 {
211 return std::max(m_maxThreadCount,1) + m_reserveThread;
212 }
213
214 mutable QMutex m_lock;
215 QString m_name;
216 QWaitCondition m_wait;
217 bool m_running {true};
218 std::chrono::milliseconds m_expiryTimeout {2min};
219 int m_maxThreadCount {QThread::idealThreadCount()};
222
224 QSet<MPoolThread*> m_availThreads;
225 QSet<MPoolThread*> m_runningThreads;
226 QList<MPoolThread*> m_deleteThreads;
227
228 static QRecursiveMutex s_pool_lock;
230 static QList<MThreadPool*> s_all_pools;
231};
232
233QRecursiveMutex MThreadPoolPrivate::s_pool_lock;
235QList<MThreadPool*> MThreadPoolPrivate::s_all_pools;
236
238
239MThreadPool::MThreadPool(const QString &name) :
240 m_priv(new MThreadPoolPrivate(name))
241{
242 QMutexLocker locker(&MThreadPoolPrivate::s_pool_lock);
243 MThreadPoolPrivate::s_all_pools.push_back(this);
244}
245
247{
248 Stop();
250 {
251 QMutexLocker locker(&MThreadPoolPrivate::s_pool_lock);
252 MThreadPoolPrivate::s_all_pools.removeAll(this);
253 }
254 delete m_priv;
255 m_priv = nullptr;
256}
257
259{
260 QMutexLocker locker(&m_priv->m_lock);
261 m_priv->m_running = false;
262 QSet<MPoolThread*>::iterator it = m_priv->m_availThreads.begin();
263 for (; it != m_priv->m_availThreads.end(); ++it)
264 (*it)->Shutdown();
265 it = m_priv->m_runningThreads.begin();
266 for (; it != m_priv->m_runningThreads.end(); ++it)
267 (*it)->Shutdown();
268 m_priv->m_wait.wakeAll();
269}
270
272{
273 waitForDone();
274
275 QMutexLocker locker(&m_priv->m_lock);
276 for (auto *thread : std::as_const(m_priv->m_availThreads))
277 {
278 m_priv->m_deleteThreads.push_front(thread);
279 }
280 m_priv->m_availThreads.clear();
281
282 while (!m_priv->m_deleteThreads.empty())
283 {
284 MPoolThread *thread = m_priv->m_deleteThreads.back();
285 locker.unlock();
286
287 thread->wait();
288
289 locker.relock();
290 delete thread;
291 if (m_priv->m_deleteThreads.back() == thread)
292 m_priv->m_deleteThreads.pop_back();
293 else
294 m_priv->m_deleteThreads.removeAll(thread);
295 }
296}
297
299{
300 QMutexLocker locker(&MThreadPoolPrivate::s_pool_lock);
302 MThreadPoolPrivate::s_pool = new MThreadPool("GlobalPool");
304}
305
307{
308 QMutexLocker locker(&MThreadPoolPrivate::s_pool_lock);
309 QList<MThreadPool*>::iterator it;
310 for (it = MThreadPoolPrivate::s_all_pools.begin();
311 it != MThreadPoolPrivate::s_all_pools.end(); ++it)
312 {
313 (*it)->Stop();
314 }
315}
316
318{
319 QMutexLocker locker(&MThreadPoolPrivate::s_pool_lock);
320 QList<MThreadPool*>::iterator it;
321 for (it = MThreadPoolPrivate::s_all_pools.begin();
322 it != MThreadPoolPrivate::s_all_pools.end(); ++it)
323 {
324 (*it)->Stop();
325 }
326 for (it = MThreadPoolPrivate::s_all_pools.begin();
327 it != MThreadPoolPrivate::s_all_pools.end(); ++it)
328 {
329 (*it)->DeletePoolThreads();
330 }
331}
332
333void MThreadPool::start(QRunnable *runnable, const QString& debugName, int priority)
334{
335 QMutexLocker locker(&m_priv->m_lock);
336 if (TryStartInternal(runnable, debugName, false))
337 return;
338
339 MPoolQueues::iterator it = m_priv->m_runQueues.find(priority);
340 if (it != m_priv->m_runQueues.end())
341 {
342 (*it).push_back(MPoolEntry(runnable,debugName));
343 }
344 else
345 {
346 MPoolQueue list;
347 list.push_back(MPoolEntry(runnable,debugName));
348 m_priv->m_runQueues[priority] = list;
349 }
350}
351
353 QRunnable *runnable, const QString& debugName,
354 std::chrono::milliseconds waitForAvailMS)
355{
356 QMutexLocker locker(&m_priv->m_lock);
357 if (waitForAvailMS > 0ms && m_priv->m_availThreads.empty() &&
359 {
360 MythTimer t;
361 t.start();
362 auto left = waitForAvailMS - t.elapsed();
363 while (left > 0ms && m_priv->m_availThreads.empty() &&
365 {
366 m_priv->m_wait.wait(locker.mutex(), left.count());
367 left = waitForAvailMS - t.elapsed();
368 }
369 }
370 TryStartInternal(runnable, debugName, true);
371}
372
373
374bool MThreadPool::tryStart(QRunnable *runnable, const QString& debugName)
375{
376 QMutexLocker locker(&m_priv->m_lock);
377 return TryStartInternal(runnable, debugName, false);
378}
379
381 QRunnable *runnable, const QString& debugName, bool reserved)
382{
383 if (!m_priv->m_running)
384 return false;
385
386 while (!m_priv->m_deleteThreads.empty())
387 {
388 m_priv->m_deleteThreads.back()->wait();
389 delete m_priv->m_deleteThreads.back();
390 m_priv->m_deleteThreads.pop_back();
391 }
392
393 for (auto iter = m_priv->m_availThreads.begin();
394 iter != m_priv->m_availThreads.end(); )
395 {
396 MPoolThread *thread = *iter;
397 iter = m_priv->m_availThreads.erase(iter);
398 m_priv->m_runningThreads.insert(thread);
399 if (reserved)
401 if (thread->SetRunnable(runnable, debugName, reserved))
402 {
403 return true;
404 }
405
406 if (reserved)
408 thread->Shutdown();
409 m_priv->m_runningThreads.remove(thread);
410 m_priv->m_deleteThreads.push_front(thread);
411 }
412
413 if (reserved ||
415 {
416 if (reserved)
418 QString name {QString("%1%2").arg(m_priv->m_name, QString::number(m_priv->m_threadsCreated))};
420 auto *thread = new MPoolThread(name, *this, m_priv->m_expiryTimeout);
421 m_priv->m_runningThreads.insert(thread);
422 thread->SetRunnable(runnable, debugName, reserved);
423 thread->start();
424 if (thread->isRunning())
425 {
426 return true;
427 }
428
429 // Thread failed to run, OOM?
430 // QThread will print an error, so we don't have to
431 if (reserved)
433 thread->Shutdown();
434 m_priv->m_runningThreads.remove(thread);
435 m_priv->m_deleteThreads.push_front(thread);
436 }
437
438 return false;
439}
440
442{
443 QMutexLocker locker(&m_priv->m_lock);
444
445 if (!m_priv->m_running)
446 {
447 m_priv->m_runningThreads.remove(thread);
448 thread->Shutdown();
449 m_priv->m_deleteThreads.push_front(thread);
450 m_priv->m_wait.wakeAll();
451 return;
452 }
453
454 MPoolQueues::iterator it = m_priv->m_runQueues.begin();
455 if (it == m_priv->m_runQueues.end())
456 {
457 m_priv->m_runningThreads.remove(thread);
458 m_priv->m_availThreads.insert(thread);
459 m_priv->m_wait.wakeAll();
460 return;
461 }
462
463 MPoolEntry e = (*it).front();
464 if (!thread->SetRunnable(e.first, e.second, false))
465 {
466 m_priv->m_runningThreads.remove(thread);
467 m_priv->m_wait.wakeAll();
468 if (!TryStartInternal(e.first, e.second, false))
469 {
470 thread->Shutdown();
471 m_priv->m_deleteThreads.push_front(thread);
472 return;
473 }
474 thread->Shutdown();
475 m_priv->m_deleteThreads.push_front(thread);
476 }
477
478 (*it).pop_front();
479 if ((*it).empty())
480 m_priv->m_runQueues.erase(it);
481}
482
484{
485 QMutexLocker locker(&m_priv->m_lock);
486 m_priv->m_runningThreads.remove(thread);
487 m_priv->m_availThreads.remove(thread);
488 if (!m_priv->m_deleteThreads.contains(thread))
489 m_priv->m_deleteThreads.push_front(thread);
490 m_priv->m_wait.wakeAll();
491}
492
493std::chrono::milliseconds MThreadPool::expiryTimeout(void) const
494{
495 QMutexLocker locker(&m_priv->m_lock);
496 return m_priv->m_expiryTimeout;
497}
498
499void MThreadPool::setExpiryTimeout(std::chrono::milliseconds expiryTimeout)
500{
501 QMutexLocker locker(&m_priv->m_lock);
503}
504
506{
507 QMutexLocker locker(&m_priv->m_lock);
508 return m_priv->m_maxThreadCount;
509}
510
511void MThreadPool::setMaxThreadCount(int maxThreadCount)
512{
513 QMutexLocker locker(&m_priv->m_lock);
515}
516
518{
519 QMutexLocker locker(&m_priv->m_lock);
520 return m_priv->m_availThreads.size() + m_priv->m_runningThreads.size();
521}
522
523/*
524void MThreadPool::reserveThread(void)
525{
526 QMutexLocker locker(&m_priv->m_lock);
527 m_priv->m_reserveThread++;
528}
529
530void MThreadPool::releaseThread(void)
531{
532 QMutexLocker locker(&m_priv->m_lock);
533 if (m_priv->m_reserveThread > 0)
534 m_priv->m_reserveThread--;
535}
536*/
537
539{
540 QMutexLocker locker(&m_priv->m_lock);
541 if (m_priv->m_reserveThread > 0)
543}
544
545#if 0
546static void print_set(QString title, QSet<MPoolThread*> set)
547{
548 LOG(VB_GENERAL, LOG_INFO, title);
549 for (auto item : std::as_const(set))
550 {
551 LOG(VB_GENERAL, LOG_INFO, QString(" : 0x%1")
552 .arg((quint64)item,0,16));
553 }
554 LOG(VB_GENERAL, LOG_INFO, "");
555}
556#endif
557
559{
560 QMutexLocker locker(&m_priv->m_lock);
561 while (true)
562 {
563 while (!m_priv->m_deleteThreads.empty())
564 {
565 m_priv->m_deleteThreads.back()->wait();
566 delete m_priv->m_deleteThreads.back();
567 m_priv->m_deleteThreads.pop_back();
568 }
569
570 if (m_priv->m_running && !m_priv->m_runQueues.empty())
571 {
572 m_priv->m_wait.wait(locker.mutex());
573 continue;
574 }
575
576 QSet<MPoolThread*> working = m_priv->m_runningThreads;
577 working = working.subtract(m_priv->m_availThreads);
578 if (working.empty())
579 break;
580 m_priv->m_wait.wait(locker.mutex());
581 }
582}
583
584/* vim: set expandtab tabstop=4 shiftwidth=4: */
void Shutdown(void)
std::chrono::milliseconds m_expiryTimeout
QWaitCondition m_wait
MPoolThread(const QString &objectName, MThreadPool &pool, std::chrono::milliseconds timeout)
bool SetRunnable(QRunnable *runnable, QString runnableName, bool reserved)
MThreadPool & m_pool
QString m_runnableName
void run(void) override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
QSet< MPoolThread * > m_runningThreads
static QList< MThreadPool * > s_all_pools
static QRecursiveMutex s_pool_lock
MThreadPoolPrivate(QString name)
QWaitCondition m_wait
std::chrono::milliseconds m_expiryTimeout
QSet< MPoolThread * > m_availThreads
MPoolQueues m_runQueues
int GetRealMaxThread(void) const
QList< MPoolThread * > m_deleteThreads
static MThreadPool * s_pool
int maxThreadCount(void) const
std::chrono::milliseconds expiryTimeout(void) const
void setExpiryTimeout(std::chrono::milliseconds expiryTimeout)
void DeletePoolThreads(void)
MThreadPool(const QString &name)
void setMaxThreadCount(int maxThreadCount)
MThreadPoolPrivate * m_priv
Definition: mthreadpool.h:57
void ReleaseThread(void)
void startReserved(QRunnable *runnable, const QString &debugName, std::chrono::milliseconds waitForAvailMS=0ms)
bool tryStart(QRunnable *runnable, const QString &debugName)
void NotifyAvailable(MPoolThread *thread)
void Stop(void)
int activeThreadCount(void) const
static void StopAllPools(void)
static MThreadPool * globalInstance(void)
friend class MPoolThread
Definition: mthreadpool.h:20
void start(QRunnable *runnable, const QString &debugName, int priority=0)
static void ShutdownAllPools(void)
void waitForDone(void)
void NotifyDone(MPoolThread *thread)
bool TryStartInternal(QRunnable *runnable, const QString &debugName, bool reserved)
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:49
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:180
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:193
QRunnable * m_runnable
Definition: mthread.h:132
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:284
QString objectName(void) const
Definition: mthread.cpp:227
A QElapsedTimer based timer to replace use of QTime as a timer.
Definition: mythtimer.h:14
void loggingDeregisterThread(void)
Deregister the current thread's name.
Definition: logging.cpp:721
void loggingRegisterThread(const QString &name)
Register the current thread with the given name.
Definition: logging.cpp:702
QList< MPoolEntry > MPoolQueue
QPair< QRunnable *, QString > MPoolEntry
Definition: mthreadpool.cpp:99
QMap< int, MPoolQueue > MPoolQueues
MythDB * GetMythDB(void)
Definition: mythdb.cpp:50
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39