diff options
author | kupries <kupries> | 2000-05-02 22:02:32 (GMT) |
---|---|---|
committer | kupries <kupries> | 2000-05-02 22:02:32 (GMT) |
commit | bfac38b888b4dee3f80767f8da8691a1154891b7 (patch) | |
tree | 73773fe6b41f1aec6a847be17c221d4a5ee4cd27 /generic/tclThreadJoin.c | |
parent | 492f9b8edd489f07ffd0741d0e9f23c0433334f9 (diff) | |
download | tcl-bfac38b888b4dee3f80767f8da8691a1154891b7.zip tcl-bfac38b888b4dee3f80767f8da8691a1154891b7.tar.gz tcl-bfac38b888b4dee3f80767f8da8691a1154891b7.tar.bz2 |
2000-05-02 Andreas Kupries <a.kupries@westend.com>
* Overall changes:
(1) Implementation of joinable threads for all platforms.
(2) Additional API's for channels. Required to allow the
thread extension to move channels between threads.
* generic/tcl.decls (lines 1360f): Added Tcl_JoinThread,
Tcl_IsChannelShared, Tcl_IsChannelRegistered, Tcl_CutChannel,
Tcl_SpliceChannel, Tcl_IsChannelExisting and
Tcl_ClearChannelHandlers (slots 394 to 400).
* generic/tclIO.c: Implemented Tcl_IsChannelRegistered,
Tcl_IsChannelShared, Tcl_CutChannel, Tcl_SpliceChannel,
Tcl_IsChannelExisting and Tcl_ClearChannelHandlers.
Tcl_CutChannel uses code from CloseChannel. Replaced this code
by a call to Tcl_CutChannel. Replaced several code fragments
adding channels to the channel list with calls to
Tcl_SpliceChannel. Removed now unused variables from
CloseChannel and Tcl_UnstackChannel. Tcl_ClearChannelHandlers
uses code from Tcl_Close. Replaced this code by a call to
Tcl_ClearChannelHandlers. Removed now unused variables from
Tcl_Close. Added the subcommands 'cut', 'forgetch', 'splice' and
'isshared' to the test code
(TclTestChannelCmd).
* unix/tclUnixThread.c: Implemented Tcl_JoinThread using the
pthread-functionality.
* win/tclWinThrd.c: Fixed several small typos in comments.
Implemented Tcl_JoinThread using a platform independent
emulation layer (see generic/tclThreadJoin.c below). Added
'joinLock' to serialize Tcl_CreateThread and TclpExitThread to
prevent a race for joinable threads.
* mac/tclMacThrd.c: Implemented Tcl_JoinThread using a platform
independent emulation layer (see generic/tclThreadJoin.c
below). Due to the cooperative nature of threading on this
platform the race mentioned above is not present.
* generic/tclThreadJoin.c: New file. Contains a platform
independent emulation layer helping in the implementation of
joinable threads for the win and mac platforms.
* generic/tclInt.h: Added declarations for TclJoinThread,
TclRememberJoinableThread and TclSignalExitThread. These
procedures define the API of the emulation layer for joinable
threads (see generic/tclThreadJoin.c above).
* win/Makefile.in:
* win/makefile.vc: Added generic/tclTheadJoin.o to the rules.
* mac/: I don't know to which file generic/tclTheadJoin.o has to
be added to so that it compiles. Sorry.
* unix/tclUnixChan.c: #ifdef'd the thread-local list of file
channels as it prevents us from transfering channels. To restore
this we may need an extended interface to drivers in the
future. Target: 9.0. Found while testing the new transfer of
channels. The information in this list for a channel was left
behind and then crashed the system during finalization.
* generic/tclThreadTest.c: Added -joinable flag to 'testthread
create'. Added subcommand 'testthread join'.
* doc/CrtChannel.3: Added documentation for Tcl_IsChannelRegistered,
Tcl_IsChannelShared, Tcl_CutChannel, Tcl_SpliceChannel,
Tcl_IsChannelExisting and Tcl_ClearChannelHandlers.
* doc/Thread.3: Added documentation for Tcl_JoinThread.
* tests/thread.test: Added tests for joining of threads.
Diffstat (limited to 'generic/tclThreadJoin.c')
-rw-r--r-- | generic/tclThreadJoin.c | 306 |
1 files changed, 306 insertions, 0 deletions
diff --git a/generic/tclThreadJoin.c b/generic/tclThreadJoin.c new file mode 100644 index 0000000..6a0d35c --- /dev/null +++ b/generic/tclThreadJoin.c @@ -0,0 +1,306 @@ +/* + * tclThreadJoin.c -- + * + * This file implements a platform independent emulation layer for + * the handling of joinable threads. The Mac and Windows platforms + * use this code to provide the functionality of joining threads. + * + * Copyright (c) 2000 by Scriptics, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id: tclThreadJoin.c,v 1.1 2000/05/02 22:02:36 kupries Exp $ + */ + +#include "tclInt.h" + +/* 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(id, result) + 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. */ +{ + /* 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. + */ + + JoinableThread* threadPtr; + + Tcl_MutexLock (joinMutex); + + for (threadPtr = firstThreadPtr; + (threadPtr != (JoinableThread*) NULL) && (threadPtr->id != id); + threadPtr = threadPtr->nextThreadPtr) + /* empty body */ + ; + + if (threadPtr == (JoinableThread*) 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; + + for (prevThreadPtr = firstThreadPtr; + prevThreadPtr->nextThreadPtr != threadPtr; + prevThreadPtr = prevThreadPtr->nextThreadPtr) + /* empty body */ + ; + + 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 ((VOID*) threadPtr); + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * TclRememberJoinableThread -- + * + * This procedure remebers a thread as joinable. Only a call to + * TclJoinThread will remove the structre 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(id) + 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(id,result) + Tcl_ThreadId id; /* Id of the thread signaling its exit */ + int result; /* The result from the thread */ +{ + JoinableThread* threadPtr; + + Tcl_MutexLock (joinMutex); + + for (threadPtr = firstThreadPtr; + (threadPtr != (JoinableThread*) NULL) && (threadPtr->id != id); + threadPtr = threadPtr->nextThreadPtr) + /* empty body */ + ; + + if (threadPtr == (JoinableThread*) 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); +} |