QGIS API Documentation 4.3.0-Master (52aa6503bed)
Loading...
Searching...
No Matches
qgsthreadingutils.h
Go to the documentation of this file.
1/***************************************************************************
2 qgsthreadingutils.h
3 --------------------------------------
4 Date : 11.9.2018
5 Copyright : (C) 2018 by Matthias Kuhn
6 email : matthias@opengis.ch
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#ifndef QGSTHREADINGUTILS_H
17#define QGSTHREADINGUTILS_H
18
19
20#include "qgsconfig.h"
21
22#include <memory>
23#include <shared_mutex>
24#include <utility>
25
26#include "qgis_core.h"
27#include "qgsfeedback.h"
28
29#include <QString>
30#include <QThread>
31
32#define SIP_NO_FILE
33
34using namespace Qt::StringLiterals;
35
36#if defined( QGISDEBUG ) || defined( AGGRESSIVE_SAFE_MODE )
37#include <QDebug>
38#include <QMutex>
39#endif
40#include <QSemaphore>
41#include <QCoreApplication>
42
43
44#if defined( Q_OS_LINUX ) && !defined( QT_LINUXBASE )
45#include <sys/prctl.h>
46#elif defined( Q_OS_FREEBSD ) || defined( Q_OS_OPENBSD )
47#include <pthread.h>
48#include <pthread_np.h>
49#endif
50
51#ifdef __clang_analyzer__
52#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS \
53 do \
54 { \
55 } while ( false );
56#elif defined( AGGRESSIVE_SAFE_MODE )
57#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS \
58 if ( QThread::currentThread() != thread() ) \
59 { \
60 qFatal( \
61 "%s", \
62 u"%2 (%1:%3) is run from a different thread than the object '%4' lives in [0x%5 vs 0x%6]"_s.arg( QString( __FILE__ ), QString( __FUNCTION__ ), QString::number( __LINE__ ), objectName() ) \
63 .arg( reinterpret_cast< qint64 >( QThread::currentThread() ), 0, 16 ) \
64 .arg( reinterpret_cast< qint64 >( thread() ), 0, 16 ) \
65 .toLocal8Bit() \
66 .constData() \
67 ); \
68 }
69#elif defined( QGISDEBUG )
70#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS \
71 if ( QThread::currentThread() != thread() ) \
72 { \
73 qWarning() << u"%2 (%1:%3) is run from a different thread than the object '%4' lives in [0x%5 vs 0x%6]"_s \
74 .arg( QString( __FILE__ ), QString( __FUNCTION__ ), QString::number( __LINE__ ), objectName() ) \
75 .arg( reinterpret_cast< qint64 >( QThread::currentThread() ), 0, 16 ) \
76 .arg( reinterpret_cast< qint64 >( thread() ), 0, 16 ) \
77 .toLocal8Bit() \
78 .constData(); \
79 }
80#else
81#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS \
82 do \
83 { \
84 } while ( false );
85#endif
86
87// !!DO NOT USE THIS FOR NEW CODE !!
88// This is in place to keep legacy code running and should be removed in the future.
89#ifdef __clang_analyzer__
90#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL \
91 do \
92 { \
93 } while ( false );
94#elif defined( QGISDEBUG )
95#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL \
96 if ( QThread::currentThread() != thread() ) \
97 { \
98 const QString location = u"%1 (%2:%3)"_s.arg( QString( __FUNCTION__ ), QString( __FILE__ ), QString::number( __LINE__ ) ); \
99 QgsThreadingUtils::sEmittedWarningMutex.lock(); \
100 if ( !QgsThreadingUtils::sEmittedWarnings.contains( location ) ) \
101 { \
102 qWarning() << u"%1 is run from a different thread than the object '%2' lives in [0x%3 vs 0x%4]"_s.arg( location, objectName() ) \
103 .arg( reinterpret_cast< qint64 >( QThread::currentThread() ), 0, 16 ) \
104 .arg( reinterpret_cast< qint64 >( thread() ), 0, 16 ) \
105 .toLocal8Bit() \
106 .constData(); \
107 QgsThreadingUtils::sEmittedWarnings.insert( location ); \
108 } \
109 QgsThreadingUtils::sEmittedWarningMutex.unlock(); \
110 }
111#else
112#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL \
113 do \
114 { \
115 } while ( false );
116#endif
117
118#ifdef __clang_analyzer__
119#define QGIS_CHECK_QOBJECT_THREAD_EQUALITY( other ) \
120 do \
121 { \
122 } while ( false ); \
123 ( void ) ( other );
124#elif defined( AGGRESSIVE_SAFE_MODE )
125#define QGIS_CHECK_QOBJECT_THREAD_EQUALITY( other ) \
126 if ( ( other )->thread() != thread() ) \
127 { \
128 qFatal( \
129 "%s", \
130 u"%2 (%1:%3) Object %4 is from a different thread than the object %5 lives in [0x%6 vs 0x%7]"_s \
131 .arg( QString( __FILE__ ), QString( __FUNCTION__ ), QString::number( __LINE__ ), ( other )->objectName(), objectName() ) \
132 .arg( reinterpret_cast< qint64 >( QThread::currentThread() ), 0, 16 ) \
133 .arg( reinterpret_cast< qint64 >( thread() ), 0, 16 ) \
134 .toLocal8Bit() \
135 .constData() \
136 ); \
137 }
138#elif defined( QGISDEBUG )
139#define QGIS_CHECK_QOBJECT_THREAD_EQUALITY( other ) \
140 if ( ( other )->thread() != thread() ) \
141 { \
142 qWarning() << u"%2 (%1:%3) Object %4 is from a different thread than the object %5 lives in [0x%6 vs 0x%7]"_s \
143 .arg( QString( __FILE__ ), QString( __FUNCTION__ ), QString::number( __LINE__ ), ( other )->objectName(), objectName() ) \
144 .arg( reinterpret_cast< qint64 >( QThread::currentThread() ), 0, 16 ) \
145 .arg( reinterpret_cast< qint64 >( thread() ), 0, 16 ) \
146 .toLocal8Bit() \
147 .constData(); \
148 }
149#else
150#define QGIS_CHECK_QOBJECT_THREAD_EQUALITY( other ) \
151 do \
152 { \
153 } while ( false ); \
154 ( void ) ( other );
155#endif
156
157#ifdef __clang_analyzer__
158#define QGIS_CHECK_OTHER_QOBJECT_THREAD_ACCESS( other ) \
159 do \
160 { \
161 } while ( false ); \
162 ( void ) ( other );
163#elif defined( AGGRESSIVE_SAFE_MODE )
164#define QGIS_CHECK_OTHER_QOBJECT_THREAD_ACCESS( other ) \
165 if ( ( other )->thread() != QThread::currentThread() ) \
166 { \
167 qFatal( \
168 "%s", \
169 u"%2 (%1:%3) Access from a different thread than the object %4 lives in [0x%5 vs 0x%6]"_s \
170 .arg( QString( __FILE__ ), QString( __FUNCTION__ ), QString::number( __LINE__ ), ( other )->objectName() ) \
171 .arg( reinterpret_cast< qint64 >( QThread::currentThread() ), 0, 16 ) \
172 .arg( reinterpret_cast< qint64 >( ( other )->thread() ), 0, 16 ) \
173 .toLocal8Bit() \
174 .constData() \
175 ); \
176 }
177#elif defined( QGISDEBUG )
178#define QGIS_CHECK_OTHER_QOBJECT_THREAD_ACCESS( other ) \
179 if ( ( other )->thread() != QThread::currentThread() ) \
180 { \
181 qWarning() << u"%2 (%1:%3) Access from a different thread than the object %4 lives in [0x%5 vs 0x%6]"_s \
182 .arg( QString( __FILE__ ), QString( __FUNCTION__ ), QString::number( __LINE__ ), ( other )->objectName() ) \
183 .arg( reinterpret_cast< qint64 >( QThread::currentThread() ), 0, 16 ) \
184 .arg( reinterpret_cast< qint64 >( ( other )->thread() ), 0, 16 ) \
185 .toLocal8Bit() \
186 .constData(); \
187 }
188#else
189#define QGIS_CHECK_OTHER_QOBJECT_THREAD_ACCESS( other ) \
190 do \
191 { \
192 } while ( false ); \
193 ( void ) ( other );
194#endif
195
196#ifdef __clang_analyzer__
197#define QGIS_CHECK_MAIN_THREAD_ACCESS \
198 do \
199 { \
200 } while ( false );
201#elif defined( AGGRESSIVE_SAFE_MODE )
202#define QGIS_CHECK_MAIN_THREAD_ACCESS \
203 if ( QThread::currentThread() != QCoreApplication::instance()->thread() ) \
204 { \
205 qFatal( \
206 "%s", \
207 u"%2 (%1:%3) must be called from the main thread [current: 0x%4, main: 0x%5]"_s.arg( QString( __FILE__ ), QString( __FUNCTION__ ), QString::number( __LINE__ ) ) \
208 .arg( reinterpret_cast< qint64 >( QThread::currentThread() ), 0, 16 ) \
209 .arg( reinterpret_cast< qint64 >( QCoreApplication::instance()->thread() ), 0, 16 ) \
210 .toLocal8Bit() \
211 .constData() \
212 ); \
213 }
214#elif defined( QGISDEBUG )
215#define QGIS_CHECK_MAIN_THREAD_ACCESS \
216 if ( QThread::currentThread() != QCoreApplication::instance()->thread() ) \
217 { \
218 qWarning() << u"%2 (%1:%3) must be called from the main thread [current: 0x%4, main: 0x%5]"_s.arg( QString( __FILE__ ), QString( __FUNCTION__ ), QString::number( __LINE__ ) ) \
219 .arg( reinterpret_cast< qint64 >( QThread::currentThread() ), 0, 16 ) \
220 .arg( reinterpret_cast< qint64 >( QCoreApplication::instance()->thread() ), 0, 16 ) \
221 .toLocal8Bit() \
222 .constData(); \
223 }
224#else
225#define QGIS_CHECK_MAIN_THREAD_ACCESS \
226 do \
227 { \
228 } while ( false );
229#endif
230
231
239{
240 public:
249 : mObject( object )
250 {
251 Q_ASSERT_X( mObject->thread() == nullptr || mObject->thread() == QThread::currentThread(), "QgsScopedAssignObjectToCurrentThread", "QObject was already assigned to a different thread!" );
252 if ( mObject->thread() != QThread::currentThread() )
253 mObject->moveToThread( QThread::currentThread() );
254 }
255
256 ~QgsScopedAssignObjectToCurrentThread() { mObject->moveToThread( nullptr ); }
257
260
261 private:
262 QObject *mObject = nullptr;
263};
264
271class CORE_EXPORT QgsThreadingUtils
272{
273 public:
290 template<typename Func> static bool runOnMainThread( const Func &func, QgsFeedback *feedback = nullptr )
291 {
292 // Make sure we only deal with the vector layer on the main thread where it lives.
293 // Anything else risks a crash.
294 if ( QThread::currentThread() == qApp->thread() )
295 {
296 func();
297 return true;
298 }
299 else
300 {
301 if ( feedback )
302 {
303 // This semaphore will block the worker thread until the main thread is ready.
304 // Ready means the event to execute the waitFunc has arrived in the event loop
305 // and is being executed.
306 QSemaphore semaphoreMainThreadReady( 1 );
307
308 // This semaphore will block the main thread until the worker thread is ready.
309 // Once the main thread is executing the waitFunc, it will wait for this semaphore
310 // to be released. This way we can make sure that
311 QSemaphore semaphoreWorkerThreadReady( 1 );
312
313 // Acquire both semaphores. We want the main thread and the current thread to be blocked
314 // until it's safe to continue.
315 semaphoreMainThreadReady.acquire();
316 semaphoreWorkerThreadReady.acquire();
317
318 const std::function<void()> waitFunc = [&semaphoreMainThreadReady, &semaphoreWorkerThreadReady]() {
319 // This function is executed on the main thread. As soon as it's executed
320 // it will tell the worker thread that the main thread is blocked by releasing
321 // the semaphore.
322 semaphoreMainThreadReady.release();
323
324 // ... and wait for the worker thread to release its semaphore
325 semaphoreWorkerThreadReady.acquire();
326 };
327
328 QMetaObject::invokeMethod( qApp, waitFunc, Qt::QueuedConnection );
329
330 // while we are in the event queue for the main thread and not yet
331 // being executed, check all 100 ms if the feedback is canceled.
332 while ( !semaphoreMainThreadReady.tryAcquire( 1, 100 ) )
333 {
334 if ( feedback->isCanceled() )
335 {
336 semaphoreWorkerThreadReady.release();
337 return false;
338 }
339 }
340
341 // finally, the main thread is blocked and we are (most likely) not canceled.
342 // let's do the real work!!
343 func();
344
345 // work done -> tell the main thread he may continue
346 semaphoreWorkerThreadReady.release();
347 return true;
348 }
349 QMetaObject::invokeMethod( qApp, func, Qt::BlockingQueuedConnection );
350 return true;
351 }
352 }
353#if defined( QGISDEBUG )
355 static QSet< QString > sEmittedWarnings;
357 static QMutex sEmittedWarningMutex;
358#endif
359
365 static QString threadDescription( QThread *thread );
366};
367
368
382{
383 public:
387 QgsScopedThreadName( const QString &name )
388 {
389#if ( defined( Q_OS_LINUX ) && !defined( QT_LINUXBASE ) ) || defined( Q_OS_FREEBSD ) || defined( Q_OS_OPENBSD )
390 mOldName = getCurrentThreadName();
391 setCurrentThreadName( name );
392#else
393 ( void ) name;
394#endif
395 }
396
401 {
402#if ( defined( Q_OS_LINUX ) && !defined( QT_LINUXBASE ) ) || defined( Q_OS_FREEBSD ) || defined( Q_OS_OPENBSD )
403 setCurrentThreadName( mOldName );
404#endif
405 }
406
407 private:
408#if ( defined( Q_OS_LINUX ) && !defined( QT_LINUXBASE ) ) || defined( Q_OS_FREEBSD ) || defined( Q_OS_OPENBSD )
409 QString mOldName;
410
411 static QString getCurrentThreadName()
412 {
413#if defined( Q_OS_LINUX ) && !defined( QT_LINUXBASE )
414 char name[16];
415 prctl( PR_GET_NAME, name, 0, 0, 0 );
416 return QString( name );
417#elif defined( Q_OS_FREEBSD ) || defined( Q_OS_OPENBSD )
418 char name[16];
419 pthread_get_name_np( pthread_self(), name, sizeof( name ) );
420 return QString( name );
421#else
422 return QString();
423#endif
424 }
425
426 static void setCurrentThreadName( const QString &name )
427 {
428#if defined( Q_OS_LINUX ) && !defined( QT_LINUXBASE )
429 prctl( PR_SET_NAME, name.toLocal8Bit().constData(), 0, 0, 0 );
430#elif defined( Q_OS_FREEBSD ) || defined( Q_OS_OPENBSD )
431 pthread_set_name_np( pthread_self(), name.toLocal8Bit().constData() );
432#else
433 ( void ) name;
434#endif
435 }
436#endif
437};
438
444template<typename T> class AtomicSharedPtr
445{
446 public:
447 AtomicSharedPtr() = default;
448
450 explicit AtomicSharedPtr( T p )
451 : p_( std::move( p ) )
452 {}
453
454 AtomicSharedPtr( const AtomicSharedPtr & ) = delete;
458
460 [[nodiscard]] T load( std::memory_order order = std::memory_order_seq_cst ) const
461 {
462 ( void ) order;
463 std::shared_lock lock( m_ );
464 return p_;
465 }
466
468 void store( T p, std::memory_order order = std::memory_order_seq_cst )
469 {
470 ( void ) order;
471 // keeps the previous owner alive so its deleter runs outside the lock
472 T old;
473 {
474 std::unique_lock lock( m_ );
475 old = std::exchange( p_, std::move( p ) );
476 }
477 }
478
479 private:
480 mutable std::shared_mutex m_;
481 T p_;
482};
483#endif
AtomicSharedPtr & operator=(AtomicSharedPtr &&)=delete
AtomicSharedPtr(T p)
Constructor for an atomic shared pointer.
AtomicSharedPtr & operator=(const AtomicSharedPtr &)=delete
AtomicSharedPtr(AtomicSharedPtr &&)=delete
AtomicSharedPtr()=default
T load(std::memory_order order=std::memory_order_seq_cst) const
Atomic load (multiple readers allowed simultaneously).
AtomicSharedPtr(const AtomicSharedPtr &)=delete
void store(T p, std::memory_order order=std::memory_order_seq_cst)
Atomic store.
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
QgsScopedAssignObjectToCurrentThread(QObject *object)
Assigns object to the current thread.
QgsScopedAssignObjectToCurrentThread & operator=(const QgsScopedAssignObjectToCurrentThread &)=delete
QgsScopedAssignObjectToCurrentThread(const QgsScopedAssignObjectToCurrentThread &other)=delete
~QgsScopedThreadName()
Restores the thread name back to its original state.
QgsScopedThreadName(const QString &name)
Constructor for QgsScopedThreadName.
Provides threading utilities for QGIS.
static bool runOnMainThread(const Func &func, QgsFeedback *feedback=nullptr)
Guarantees that func is executed on the main thread.