1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
|
/*
* tclThreadJoin.c --
*
* This file implements a platform independent emulation layer for the
* handling of joinable threads. The Windows platform uses this code to
* provide the functionality of joining threads. This code is currently
* not necessary on Unix.
*
* Copyright © 2000 Scriptics Corporation
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
#include "tclInt.h"
#ifdef _WIN32
/*
* The information about each joinable thread is remembered in a structure as
* defined below.
*/
typedef struct JoinableThread {
Tcl_ThreadId id; /* The id of the joinable thread. */
int result; /* A place for the result after the demise of
* the thread. */
int done; /* Boolean flag. Initialized to 0 and set to 1
* after the exit of the thread. This allows a
* thread requesting a join to detect when
* waiting is not necessary. */
int waitedUpon; /* Boolean flag. Initialized to 0 and set to 1
* by the thread waiting for this one via
* Tcl_JoinThread. Used to lock any other
* thread trying to wait on this one. */
Tcl_Mutex threadMutex; /* The mutex used to serialize access to this
* structure. */
Tcl_Condition cond; /* This is the condition a thread has to wait
* upon to get notified of the end of the
* described thread. It is signaled indirectly
* by Tcl_ExitThread. */
struct JoinableThread *nextThreadPtr;
/* Reference to the next thread in the list of
* joinable threads. */
} JoinableThread;
/*
* The following variable is used to maintain the global list of all joinable
* threads. Usage by a thread is allowed only if the thread acquired the
* 'joinMutex'.
*/
TCL_DECLARE_MUTEX(joinMutex)
static JoinableThread *firstThreadPtr;
/*
*----------------------------------------------------------------------
*
* TclJoinThread --
*
* This procedure waits for the exit of the thread with the specified id
* and returns its result.
*
* Results:
* A standard tcl result signaling the overall success/failure of the
* operation and an integer result delivered by the thread which was
* waited upon.
*
* Side effects:
* Deallocates the memory allocated by TclRememberJoinableThread.
* Removes the data associated to the thread waited upon from the list of
* joinable threads.
*
*----------------------------------------------------------------------
*/
int
TclJoinThread(
Tcl_ThreadId id, /* The id of the thread to wait upon. */
int *result) /* Reference to a location for the result of
* the thread we are waiting upon. */
{
JoinableThread *threadPtr;
/*
* Steps done here:
* i. Acquire the joinMutex and search for the thread.
* ii. Error out if it could not be found.
* iii. If found, switch from exclusive access to the list to exclusive
* access to the thread structure.
* iv. Error out if some other is already waiting.
* v. Skip the waiting part of the thread is already done.
* vi. Wait for the thread to exit, mark it as waited upon too.
* vii. Get the result form the structure,
* viii. switch to exclusive access of the list,
* ix. remove the structure from the list,
* x. then switch back to exclusive access to the structure
* xi. and delete it.
*/
Tcl_MutexLock(&joinMutex);
threadPtr = firstThreadPtr;
while (threadPtr!=NULL && threadPtr->id!=id) {
threadPtr = threadPtr->nextThreadPtr;
}
if (threadPtr == NULL) {
/*
* Thread not found. Either not joinable, or already waited upon and
* exited. Whatever, an error is in order.
*/
Tcl_MutexUnlock(&joinMutex);
return TCL_ERROR;
}
/*
* [1] If we don't lock the structure before giving up exclusive access to
* the list some other thread just completing its wait on the same thread
* can delete the structure from under us, leaving us with a dangling
* pointer.
*/
Tcl_MutexLock(&threadPtr->threadMutex);
Tcl_MutexUnlock(&joinMutex);
/*
* [2] Now that we have the structure mutex any other thread that just
* tries to delete structure will wait at location [3] until we are done
* with the structure. And in that case we are done with it rather quickly
* as 'waitedUpon' will be set and we will have to error out.
*/
if (threadPtr->waitedUpon) {
Tcl_MutexUnlock(&threadPtr->threadMutex);
return TCL_ERROR;
}
/*
* We are waiting now, let other threads recognize this.
*/
threadPtr->waitedUpon = 1;
while (!threadPtr->done) {
Tcl_ConditionWait(&threadPtr->cond, &threadPtr->threadMutex, NULL);
}
/*
* We have to release the structure before trying to access the list again
* or we can run into deadlock with a thread at [1] (see above) because of
* us holding the structure and the other holding the list. There is no
* problem with dangling pointers here as 'waitedUpon == 1' is still valid
* and any other thread will error out and not come to this place. IOW,
* the fact that we are here also means that no other thread came here
* before us and is able to delete the structure.
*/
Tcl_MutexUnlock(&threadPtr->threadMutex);
Tcl_MutexLock(&joinMutex);
/*
* We have to search the list again as its structure may (may, almost
* certainly) have changed while we were waiting. Especially now is the
* time to compute the predecessor in the list. Any earlier result can be
* dangling by now.
*/
if (firstThreadPtr == threadPtr) {
firstThreadPtr = threadPtr->nextThreadPtr;
} else {
JoinableThread *prevThreadPtr = firstThreadPtr;
while (prevThreadPtr->nextThreadPtr != threadPtr) {
prevThreadPtr = prevThreadPtr->nextThreadPtr;
}
prevThreadPtr->nextThreadPtr = threadPtr->nextThreadPtr;
}
Tcl_MutexUnlock(&joinMutex);
/*
* [3] Now that the structure is not part of the list anymore no other
* thread can acquire its mutex from now on. But it is possible that
* another thread is still holding the mutex though, see location [2]. So
* we have to acquire the mutex one more time to wait for that thread to
* finish. We can (and have to) release the mutex immediately.
*/
Tcl_MutexLock(&threadPtr->threadMutex);
Tcl_MutexUnlock(&threadPtr->threadMutex);
/*
* Copy the result to us, finalize the synchronisation objects, then free
* the structure and return.
*/
*result = threadPtr->result;
Tcl_ConditionFinalize(&threadPtr->cond);
Tcl_MutexFinalize(&threadPtr->threadMutex);
Tcl_Free(threadPtr);
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* TclRememberJoinableThread --
*
* This procedure remembers a thread as joinable. Only a call to
* TclJoinThread will remove the structure created (and initialized) here.
* IOW, not waiting upon a joinable thread will cause memory leaks.
*
* Results:
* None.
*
* Side effects:
* Allocates memory, adds it to the global list of all joinable threads.
*
*----------------------------------------------------------------------
*/
void
TclRememberJoinableThread(
Tcl_ThreadId id) /* The thread to remember as joinable */
{
JoinableThread *threadPtr;
threadPtr = (JoinableThread *)Tcl_Alloc(sizeof(JoinableThread));
threadPtr->id = id;
threadPtr->done = 0;
threadPtr->waitedUpon = 0;
threadPtr->threadMutex = (Tcl_Mutex) NULL;
threadPtr->cond = (Tcl_Condition) NULL;
Tcl_MutexLock(&joinMutex);
threadPtr->nextThreadPtr = firstThreadPtr;
firstThreadPtr = threadPtr;
Tcl_MutexUnlock(&joinMutex);
}
/*
*----------------------------------------------------------------------
*
* TclSignalExitThread --
*
* This procedure signals that the specified thread is done with its
* work. If the thread is joinable this signal is propagated to the
* thread waiting upon it.
*
* Results:
* None.
*
* Side effects:
* Modifies the associated structure to hold the result.
*
*----------------------------------------------------------------------
*/
void
TclSignalExitThread(
Tcl_ThreadId id, /* Id of the thread signaling its exit. */
int result) /* The result from the thread. */
{
JoinableThread *threadPtr;
Tcl_MutexLock(&joinMutex);
threadPtr = firstThreadPtr;
while ((threadPtr != NULL) && (threadPtr->id != id)) {
threadPtr = threadPtr->nextThreadPtr;
}
if (threadPtr == NULL) {
/*
* Thread not found. Not joinable. No problem, nothing to do.
*/
Tcl_MutexUnlock(&joinMutex);
return;
}
/*
* Switch over the exclusive access from the list to the structure, then
* store the result, set the flag and notify the waiting thread, provided
* that it exists. The order of lock/unlock ensures that a thread entering
* 'TclJoinThread' will not interfere with us.
*/
Tcl_MutexLock(&threadPtr->threadMutex);
Tcl_MutexUnlock(&joinMutex);
threadPtr->done = 1;
threadPtr->result = result;
if (threadPtr->waitedUpon) {
Tcl_ConditionNotify(&threadPtr->cond);
}
Tcl_MutexUnlock(&threadPtr->threadMutex);
}
#else
TCL_MAC_EMPTY_FILE(generic_tclThreadJoin_c)
#endif /* _WIN32 */
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
* fill-column: 78
* End:
*/
|