summaryrefslogtreecommitdiffstats
path: root/generic/tclThreadJoin.c
diff options
context:
space:
mode:
authorkupries <kupries>2000-05-02 22:02:32 (GMT)
committerkupries <kupries>2000-05-02 22:02:32 (GMT)
commitbfac38b888b4dee3f80767f8da8691a1154891b7 (patch)
tree73773fe6b41f1aec6a847be17c221d4a5ee4cd27 /generic/tclThreadJoin.c
parent492f9b8edd489f07ffd0741d0e9f23c0433334f9 (diff)
downloadtcl-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.c306
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);
+}