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 "qthreadstorage.h"
5
6#include "private/qcoreapplication_p.h"
7#include "qthread.h"
8#include "qthread_p.h"
9#include "qmutex.h"
10
11#include <string.h>
12
13QT_BEGIN_NAMESPACE
14
15// #define THREADSTORAGE_DEBUG
16#ifdef THREADSTORAGE_DEBUG
17# define DEBUG_MSG qtsDebug
18
19# include <stdio.h>
20# include <stdarg.h>
21void qtsDebug(const char *fmt, ...)
22{
23 va_list va;
24 va_start(va, fmt);
25
26 fprintf(stderr, "QThreadStorage: ");
27 vfprintf(stderr, fmt, va);
28 fprintf(stderr, "\n");
29
30 va_end(va);
31}
32#else
33# define DEBUG_MSG if (false)qDebug
34#endif
35
36Q_CONSTINIT static QBasicMutex destructorsMutex;
37typedef QList<void (*)(void *)> DestructorMap;
38Q_GLOBAL_STATIC(DestructorMap, destructors)
39
40QThreadStorageData::QThreadStorageData(void (*func)(void *))
41{
42 QMutexLocker locker(&destructorsMutex);
43 DestructorMap *destr = destructors();
44 if (!destr) {
45 /*
46 the destructors vector has already been destroyed, yet a new
47 QThreadStorage is being allocated. this can only happen during global
48 destruction, at which point we assume that there is only one thread.
49 in order to keep QThreadStorage working, we need somewhere to store
50 the data, best place we have in this situation is at the tail of the
51 current thread's tls vector. the destructor is ignored, since we have
52 no where to store it, and no way to actually call it.
53 */
54 QThreadData *data = QThreadData::current();
55 id = data->tls.size();
56 DEBUG_MSG(msg: "QThreadStorageData: Allocated id %d, destructor %p cannot be stored", id, func);
57 return;
58 }
59 for (id = 0; id < destr->size(); id++) {
60 if (destr->at(i: id) == nullptr)
61 break;
62 }
63 if (id == destr->size()) {
64 destr->append(t: func);
65 } else {
66 (*destr)[id] = func;
67 }
68 DEBUG_MSG(msg: "QThreadStorageData: Allocated id %d, destructor %p", id, func);
69}
70
71QThreadStorageData::~QThreadStorageData()
72{
73 DEBUG_MSG(msg: "QThreadStorageData: Released id %d", id);
74 QMutexLocker locker(&destructorsMutex);
75 if (destructors())
76 (*destructors())[id] = nullptr;
77}
78
79void **QThreadStorageData::get() const
80{
81 QThreadData *data = QThreadData::current();
82 if (!data) {
83 qWarning(msg: "QThreadStorage::get: QThreadStorage can only be used with threads started with QThread");
84 return nullptr;
85 }
86 QList<void *> &tls = data->tls;
87 if (tls.size() <= id)
88 tls.resize(size: id + 1);
89 void **v = &tls[id];
90
91 DEBUG_MSG(msg: "QThreadStorageData: Returning storage %d, data %p, for thread %p",
92 id,
93 *v,
94 data->thread.loadRelaxed());
95
96 return *v ? v : nullptr;
97}
98
99void **QThreadStorageData::set(void *p)
100{
101 QThreadData *data = QThreadData::current();
102 if (!data) {
103 qWarning(msg: "QThreadStorage::set: QThreadStorage can only be used with threads started with QThread");
104 return nullptr;
105 }
106 QList<void *> &tls = data->tls;
107 if (tls.size() <= id)
108 tls.resize(size: id + 1);
109
110 void *&value = tls[id];
111 // delete any previous data
112 if (value != nullptr) {
113 DEBUG_MSG(msg: "QThreadStorageData: Deleting previous storage %d, data %p, for thread %p",
114 id,
115 value,
116 data->thread.loadRelaxed());
117
118 QMutexLocker locker(&destructorsMutex);
119 DestructorMap *destr = destructors();
120 void (*destructor)(void *) = destr ? destr->value(i: id) : nullptr;
121 locker.unlock();
122
123 void *q = value;
124 value = nullptr;
125
126 if (destructor)
127 destructor(q);
128 }
129
130 // store new data
131 value = p;
132 DEBUG_MSG(msg: "QThreadStorageData: Set storage %d for thread %p to %p", id, data->thread.loadRelaxed(), p);
133 return &value;
134}
135
136void QThreadStorageData::finish(void **p)
137{
138 QList<void *> *tls = reinterpret_cast<QList<void *> *>(p);
139 if (!tls || tls->isEmpty() || !destructors())
140 return; // nothing to do
141
142 DEBUG_MSG(msg: "QThreadStorageData: Destroying storage for thread %p", QThread::currentThread());
143 while (!tls->isEmpty()) {
144 void *&value = tls->last();
145 void *q = value;
146 value = nullptr;
147 int i = tls->size() - 1;
148 tls->resize(size: i);
149
150 if (!q) {
151 // data already deleted
152 continue;
153 }
154
155 QMutexLocker locker(&destructorsMutex);
156 void (*destructor)(void *) = destructors()->value(i);
157 locker.unlock();
158
159 if (!destructor) {
160 if (QCoreApplicationPrivate::isAlive())
161 qWarning(msg: "QThreadStorage: entry %d destroyed before end of thread %p",
162 i, QThread::currentThread());
163 continue;
164 }
165 destructor(q); //crash here might mean the thread exited after qthreadstorage was destroyed
166
167 if (tls->size() > i) {
168 //re reset the tls in case it has been recreated by its own destructor.
169 (*tls)[i] = nullptr;
170 }
171 }
172 tls->clear();
173}
174
175/*!
176 \class QThreadStorage
177 \inmodule QtCore
178 \brief The QThreadStorage class provides per-thread data storage.
179
180 \threadsafe
181
182 \ingroup thread
183
184 QThreadStorage is a template class that provides per-thread data
185 storage.
186
187 The setLocalData() function stores a single thread-specific value
188 for the calling thread. The data can be accessed later using
189 localData().
190
191 The hasLocalData() function allows the programmer to determine if
192 data has previously been set using the setLocalData() function.
193 This is also useful for lazy initialization.
194
195 If T is a pointer type, QThreadStorage takes ownership of the data
196 (which must be created on the heap with \c new) and deletes it when
197 the thread exits, either normally or via termination.
198
199 For example, the following code uses QThreadStorage to store a
200 single cache for each thread that calls the cacheObject() and
201 removeFromCache() functions. The cache is automatically
202 deleted when the calling thread exits.
203
204 \snippet threads/threads.cpp 7
205 \snippet threads/threads.cpp 8
206 \snippet threads/threads.cpp 9
207
208 \section1 Caveats
209
210 \list
211
212 \li The QThreadStorage destructor does not delete per-thread data.
213 QThreadStorage only deletes per-thread data when the thread exits
214 or when setLocalData() is called multiple times.
215
216 \li QThreadStorage can be used to store data for the \c main()
217 thread. QThreadStorage deletes all data set for the \c main()
218 thread when QApplication is destroyed, regardless of whether or
219 not the \c main() thread has actually finished.
220
221 \endlist
222
223 \sa QThread
224*/
225
226/*!
227 \fn template <class T> QThreadStorage<T>::QThreadStorage()
228
229 Constructs a new per-thread data storage object.
230*/
231
232/*!
233 \fn template <class T> QThreadStorage<T>::~QThreadStorage()
234
235 Destroys the per-thread data storage object.
236
237 Note: The per-thread data stored is not deleted. Any data left
238 in QThreadStorage is leaked. Make sure that all threads using
239 QThreadStorage have exited before deleting the QThreadStorage.
240
241 \sa hasLocalData()
242*/
243
244/*!
245 \fn template <class T> bool QThreadStorage<T>::hasLocalData() const
246
247 If T is a pointer type, returns \c true if the calling thread has
248 non-zero data available.
249
250 If T is a value type, returns whether the data has already been
251 constructed by calling setLocalData or localData.
252
253 \sa localData()
254*/
255
256/*!
257 \fn template <class T> T &QThreadStorage<T>::localData()
258
259 Returns a reference to the data that was set by the calling
260 thread.
261
262 If no data has been set, this will create a default constructed
263 instance of type T.
264
265 \sa hasLocalData()
266*/
267
268/*!
269 \fn template <class T> const T QThreadStorage<T>::localData() const
270 \overload
271
272 Returns a copy of the data that was set by the calling thread.
273
274 \sa hasLocalData()
275*/
276
277/*!
278 \fn template <class T> void QThreadStorage<T>::setLocalData(T data)
279
280 Sets the local data for the calling thread to \a data. It can be
281 accessed later using the localData() functions.
282
283 If T is a pointer type, QThreadStorage takes ownership of the data
284 and deletes it automatically either when the thread exits (either
285 normally or via termination) or when setLocalData() is called again.
286
287 \sa localData(), hasLocalData()
288*/
289
290QT_END_NAMESPACE
291

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtbase/src/corelib/thread/qthreadstorage.cpp