Compare commits

...
10 changed files with 362 additions and 44 deletions
+14 -2
View File
@@ -9,7 +9,7 @@
.TH Threads 3 "8.1" Tcl "Tcl Library Procedures"
.BS
.SH NAME
Tcl_ConditionNotify, Tcl_ConditionWait, Tcl_ConditionFinalize, Tcl_GetThreadData, Tcl_MutexLock, Tcl_MutexUnlock, Tcl_MutexFinalize, Tcl_CreateThread, Tcl_JoinThread \- Tcl thread support
Tcl_ConditionNotify, Tcl_ConditionWait, Tcl_ConditionFinalize, Tcl_GetThreadData, Tcl_UnsetThreadData, Tcl_MutexLock, Tcl_MutexUnlock, Tcl_MutexFinalize, Tcl_CreateThread, Tcl_JoinThread \- Tcl thread support
.SH SYNOPSIS
.nf
\fB#include <tcl.h>\fR
@@ -27,6 +27,9 @@ Void *
\fBTcl_GetThreadData\fR(\fIkeyPtr, size\fR)
.sp
void
\fBTcl_UnsetThreadData\fR(\fIkeyPtr, size, all\fR)
.sp
void
\fBTcl_MutexLock\fR(\fImutexPtr\fR)
.sp
void
@@ -53,10 +56,13 @@ Note that a polling value of 0 seconds does not make much sense.
This identifies a block of thread local storage. The key should be
static and process-wide, yet each thread will end up associating
a different block of storage with this key.
.AP int *size in
.AP int size in
The size of the thread local storage block. This amount of data
is allocated and initialized to zero the first time each thread
calls \fBTcl_GetThreadData\fR.
.AP int all in
Non-zero if the thread local storage block is to be unset for all
threads.
.AP Tcl_ThreadId *idPtr out
The referred storage will contain the id of the newly created thread as
returned by the operating system.
@@ -129,6 +135,12 @@ thread-private data. Its argument is a key that is shared by all threads
and a size for the block of storage. The storage is automatically
allocated and initialized to all zeros the first time each thread asks for it.
The storage is automatically deallocated by \fBTcl_FinalizeThread\fR.
.PP
The \fBTcl_UnsetThreadData\fR function is used to manually destroy
and remove the block of thread local storage. Normally, calling this
function is not necessary. However, if an individual thread local
storage block needs to be manually removed, this function provides a
means to do it.
.SS "SYNCHRONIZATION AND COMMUNICATION"
Tcl provides \fBTcl_ThreadQueueEvent\fR and \fBTcl_ThreadAlert\fR
for handling event queuing in multithreaded applications. See
+3
View File
@@ -2108,6 +2108,9 @@ declare 578 {
declare 579 {
void Tcl_AppendPrintfToObj(Tcl_Obj *objPtr, const char *format, ...)
}
declare 580 {
void Tcl_UnsetThreadData(Tcl_ThreadDataKey *keyPtr, int size, int all)
}
declare 630 {
void TclUnusedStubEntry(void)
}
+11 -3
View File
@@ -3408,7 +3408,12 @@ EXTERN Tcl_Obj * Tcl_ObjPrintf(CONST char *format, ...);
EXTERN void Tcl_AppendPrintfToObj(Tcl_Obj *objPtr,
CONST char *format, ...);
#endif
/* Slot 580 is reserved */
#ifndef Tcl_UnsetThreadData_TCL_DECLARED
#define Tcl_UnsetThreadData_TCL_DECLARED
/* 580 */
EXTERN void Tcl_UnsetThreadData(Tcl_ThreadDataKey *keyPtr,
int size, int all);
#endif
/* Slot 581 is reserved */
/* Slot 582 is reserved */
/* Slot 583 is reserved */
@@ -4078,7 +4083,7 @@ typedef struct TclStubs {
int (*tcl_AppendFormatToObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, CONST char *format, int objc, Tcl_Obj *CONST objv[]); /* 577 */
Tcl_Obj * (*tcl_ObjPrintf) (CONST char *format, ...); /* 578 */
void (*tcl_AppendPrintfToObj) (Tcl_Obj *objPtr, CONST char *format, ...); /* 579 */
VOID *reserved580;
void (*tcl_UnsetThreadData) (Tcl_ThreadDataKey *keyPtr, int size, int all); /* 580 */
VOID *reserved581;
VOID *reserved582;
VOID *reserved583;
@@ -6483,7 +6488,10 @@ extern TclStubs *tclStubsPtr;
#define Tcl_AppendPrintfToObj \
(tclStubsPtr->tcl_AppendPrintfToObj) /* 579 */
#endif
/* Slot 580 is reserved */
#ifndef Tcl_UnsetThreadData
#define Tcl_UnsetThreadData \
(tclStubsPtr->tcl_UnsetThreadData) /* 580 */
#endif
/* Slot 581 is reserved */
/* Slot 582 is reserved */
/* Slot 583 is reserved */
+11 -2
View File
@@ -1230,13 +1230,17 @@ typedef void **TclHandle;
MODULE_SCOPE void * TclThreadDataKeyGet(Tcl_ThreadDataKey *keyPtr);
MODULE_SCOPE void TclThreadDataKeySet(Tcl_ThreadDataKey *keyPtr,
void *data);
MODULE_SCOPE void TclThreadFreeData(void *blockPtr, int size);
/*
* This is a convenience macro used to initialize a thread local storage ptr.
*/
#define TCL_TSD_INIT(keyPtr) \
(ThreadSpecificData *)Tcl_GetThreadData((keyPtr), sizeof(ThreadSpecificData))
#define TCL_TSD_INIT(keyPtr) (ThreadSpecificData *) \
Tcl_GetThreadData((keyPtr), sizeof(ThreadSpecificData))
#define TCL_TSD_TERM(keyPtr) (ThreadSpecificData *) \
Tcl_UnsetThreadData((keyPtr), sizeof(ThreadSpecificData), 0)
/*
*----------------------------------------------------------------
@@ -2665,6 +2669,7 @@ MODULE_SCOPE int TclpThreadCreate(Tcl_ThreadId *idPtr,
MODULE_SCOPE int TclpFindVariable(const char *name, int *lengthPtr);
MODULE_SCOPE void TclpInitLibraryPath(char **valuePtr,
int *lengthPtr, Tcl_Encoding *encodingPtr);
MODULE_SCOPE void TclpResetLocks(void);
MODULE_SCOPE void TclpInitLock(void);
MODULE_SCOPE void TclpInitPlatform(void);
MODULE_SCOPE void TclpInitUnlock(void);
@@ -2705,6 +2710,10 @@ MODULE_SCOPE void TclpUnloadFile(Tcl_LoadHandle loadHandle);
MODULE_SCOPE void * TclpThreadDataKeyGet(Tcl_ThreadDataKey *keyPtr);
MODULE_SCOPE void TclpThreadDataKeySet(Tcl_ThreadDataKey *keyPtr,
void *data);
MODULE_SCOPE void TclpThreadDataKeyUnset(Tcl_ThreadDataKey *keyPtr,
int freeValue);
MODULE_SCOPE void TclpThreadDataKeyUnsetAll(Tcl_ThreadDataKey *keyPtr,
int freeValue);
MODULE_SCOPE void TclpThreadExit(int status);
MODULE_SCOPE size_t TclpThreadGetStackSize(void);
MODULE_SCOPE void TclRememberCondition(Tcl_Condition *mutex);
+1 -1
View File
@@ -1340,7 +1340,7 @@ TclStubs tclStubs = {
Tcl_AppendFormatToObj, /* 577 */
Tcl_ObjPrintf, /* 578 */
Tcl_AppendPrintfToObj, /* 579 */
NULL, /* 580 */
Tcl_UnsetThreadData, /* 580 */
NULL, /* 581 */
NULL, /* 582 */
NULL, /* 583 */
+64
View File
@@ -101,6 +101,70 @@ Tcl_GetThreadData(
return result;
}
/*
*----------------------------------------------------------------------
*
* Tcl_UnsetThreadData --
*
* This function finalizes and deallocates a chunk of thread local
* storage.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
void
Tcl_UnsetThreadData(
Tcl_ThreadDataKey *keyPtr, /* Identifier for the data chunk */
int size, /* Size of storage block */
int all) /* Unset for all threads? */
{
#ifdef TCL_THREADS
void *result = TclpThreadDataKeyGet(keyPtr);
if (all) {
TclpThreadDataKeyUnsetAll(keyPtr, 1 /* free */);
} else {
TclpThreadDataKeyUnset(keyPtr, 1 /* free */);
}
#else
void *result = *keyPtr;
TclThreadFreeData(result, size);
ForgetSyncObject((char *) keyPtr, &keyRecord);
#endif /* TCL_THREADS */
}
/*
*----------------------------------------------------------------------
*
* TclThreadFreeData --
*
* This function deallocates a chunk of thread local storage.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
void
TclThreadFreeData(
void *blockPtr, /* Pointer to thread local data chunk */
int size) /* Size of storage block */
{
if (blockPtr != NULL) {
memset(blockPtr, 0, (size_t) size);
ckfree((char *) blockPtr);
}
}
/*
*----------------------------------------------------------------------
*
+90
View File
@@ -397,6 +397,96 @@ TclpThreadDataKeySet(
Tcl_SetHashValue(hPtr, data);
}
/*
*----------------------------------------------------------------------
*
* TclpThreadDataKeyUnset --
*
* This procedure removes the pointer to a block of thread local
* storage.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
void
TclpThreadDataKeyUnset(
Tcl_ThreadDataKey *keyPtr, /* Identifier for the data chunk, really
* (pthread_key_t **) */
int freeValue) /* Also free the value? */
{
Tcl_HashTable *hashTablePtr;
Tcl_HashEntry *hPtr;
hashTablePtr = ThreadStorageGetHashTable(Tcl_GetCurrentThread());
hPtr = Tcl_FindHashEntry(hashTablePtr, (char *) keyPtr);
if (hPtr != NULL) {
if (freeValue) {
void *blockPtr = Tcl_GetHashValue(hPtr);
if (blockPtr != NULL) {
ckfree(blockPtr);
}
}
Tcl_DeleteHashEntry(hPtr);
}
}
/*
*----------------------------------------------------------------------
*
* TclpThreadDataKeyUnsetAll --
*
* This procedure removes the pointer to a block of thread local
* storage from all threads.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
void
TclpThreadDataKeyUnsetAll(
Tcl_ThreadDataKey *keyPtr, /* Identifier for the data chunk, really
* (pthread_key_t **) */
int freeValue) /* Also free the value? */
{
Tcl_HashEntry *hPtr;
Tcl_HashSearch search;
Tcl_MutexLock(&threadStorageLock);
for (hPtr = Tcl_FirstHashEntry(&threadStorageHashTable, &search);
hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
Tcl_HashTable *hashTablePtr = Tcl_GetHashValue(hPtr);
if (hashTablePtr != NULL) {
Tcl_HashEntry *hPtr2 = Tcl_FindHashEntry(hashTablePtr,
(char *) keyPtr);
if (hPtr2 != NULL) {
if (freeValue) {
void *blockPtr = Tcl_GetHashValue(hPtr2);
if (blockPtr != NULL) {
ckfree(blockPtr);
}
}
Tcl_DeleteHashEntry(hPtr2);
}
}
}
Tcl_MutexUnlock(&threadStorageLock);
}
/*
*----------------------------------------------------------------------
*
+115 -36
View File
@@ -127,15 +127,6 @@ static Tcl_ThreadDataKey dataKey;
static int notifierCount = 0;
/*
* The following static stores the process ID of the initialized notifier
* thread. If it changes, we have passed a fork and we should start a new
* notifier thread.
*
* You must hold the notifierMutex lock before accessing this variable.
*/
static pid_t processIDInitialized = 0;
/*
* The following variable points to the head of a doubly-linked list of
* ThreadSpecificData structures for all threads that are currently waiting on
@@ -204,9 +195,9 @@ static Tcl_ThreadId notifierThread;
static void NotifierThreadProc(ClientData clientData);
#if defined(HAVE_PTHREAD_ATFORK) && !defined(__APPLE__)
static int atForkInit = 0;
static void AtForkPrepare(void);
static void AtForkParent(void);
static void AtForkChild(void);
static void AtForkPrepareProc(void);
static void AtForkParentProc(void);
static void AtForkChildProc(void);
#endif /* HAVE_PTHREAD_ATFORK */
#endif /* TCL_THREADS */
static int FileHandlerEventProc(Tcl_Event *evPtr, int flags);
@@ -282,14 +273,16 @@ Tcl_InitNotifier(void)
*/
Tcl_MutexLock(&notifierMutex);
#if defined(HAVE_PTHREAD_ATFORK) && !defined(__APPLE__)
/*
* Install pthread_atfork handlers to reinitialize the notifier in the
* child of a fork.
* Install pthread_atfork() handlers to deal with the notifier in the
* forked child.
*/
if (!atForkInit) {
int result = pthread_atfork(AtForkPrepare, AtForkParent, AtForkChild);
int result = pthread_atfork(AtForkPrepareProc, AtForkParentProc,
AtForkChildProc);
if (result) {
Tcl_Panic("Tcl_InitNotifier: pthread_atfork failed");
@@ -297,23 +290,12 @@ Tcl_InitNotifier(void)
atForkInit = 1;
}
#endif /* HAVE_PTHREAD_ATFORK */
/*
* Check if my process id changed, e.g. I was forked
* In this case, restart the notifier thread and close the
* pipe to the original notifier thread
*/
if (notifierCount > 0 && processIDInitialized != getpid()) {
notifierCount = 0;
processIDInitialized = 0;
close(triggerPipe);
triggerPipe = -1;
}
if (notifierCount == 0) {
if (TclpThreadCreate(&notifierThread, NotifierThreadProc, NULL,
TCL_THREAD_STACK_DEFAULT, TCL_THREAD_JOINABLE) != TCL_OK) {
Tcl_Panic("Tcl_InitNotifier: unable to start notifier thread");
}
processIDInitialized = getpid();
}
notifierCount++;
@@ -1277,7 +1259,7 @@ NotifierThreadProc(
/*
*----------------------------------------------------------------------
*
* AtForkPrepare --
* AtForkPrepareProc --
*
* Lock the notifier in preparation for a fork.
*
@@ -1291,14 +1273,15 @@ NotifierThreadProc(
*/
static void
AtForkPrepare(void)
AtForkPrepareProc(void)
{
Tcl_MutexLock(&notifierMutex);
}
/*
*----------------------------------------------------------------------
*
* AtForkParent --
* AtForkParentProc --
*
* Unlock the notifier in the parent after a fork.
*
@@ -1312,16 +1295,18 @@ AtForkPrepare(void)
*/
static void
AtForkParent(void)
AtForkParentProc(void)
{
Tcl_MutexUnlock(&notifierMutex);
}
/*
*----------------------------------------------------------------------
*
* AtForkChild --
* AtForkChildProc --
*
* Unlock and reinstall the notifier in the child after a fork.
* Assumes the notifier mutex is already held. Reset the notifier
* state and then re-initializes it for the child process.
*
* Results:
* None.
@@ -1333,11 +1318,105 @@ AtForkParent(void)
*/
static void
AtForkChild(void)
AtForkChildProc(void)
{
notifierMutex = NULL;
int shouldInitNotifier = (notifierCount > 0); /* Only when in use? */
struct ThreadSpecificData *tsdPtr;
/*
* Close the trigger pipe and reset it to the default value. This
* should cause it to be re-opened when the notifier is subsequently
* initialized. Since the trigger pipe is opened via the notifier
* thread (which may never have started?), check it before attempting
* to close it.
*/
if (triggerPipe >= 0) {
close(triggerPipe);
triggerPipe = -1;
}
/*
* Since the notifier thread is not really running, zero out its ID.
* This step is not strictly necessary since its value will not be
* checked before it is re-created.
*/
notifierThread = (Tcl_ThreadId)0;
/*
* Reset the notifier reference count to zero (i.e. not initialized).
* This should force the notifier initialization code to be re-run the
* very next time Tcl_InitNotifier is called, which will be at the end
* of this function.
*/
notifierCount = 0;
/*
* Finalize and reset the condition variable. This should force it to
* be re-created during the next call to Tcl_ConditionWait().
*/
Tcl_ConditionFinalize(&notifierCV);
notifierCV = NULL;
Tcl_InitNotifier();
/*
* Free all the thread specific data for the notifier now. Since the
* child has no other threads, this data should be completely useless
* at this point.
*/
for (tsdPtr = waitingListPtr; tsdPtr; tsdPtr = tsdPtr->nextPtr) {
FileHandler *filePtr;
for (filePtr = tsdPtr->firstFileHandlerPtr; filePtr != NULL;
/* NO LOOP UPDATE */) {
FileHandler *nextFilePtr = filePtr->nextPtr;
ckfree((char *) filePtr);
filePtr = nextFilePtr;
}
Tcl_ConditionFinalize(&(tsdPtr->waitCV));
}
waitingListPtr = NULL;
/*
* Release, finalize, and reset the notifier mutex (which has been
* held since the AtForkPrepareProc() was called via pthread_atfork()).
* This should force it to be re-created during the next call to
* Tcl_MutexLock().
*/
Tcl_MutexUnlock(&notifierMutex);
Tcl_MutexFinalize(&notifierMutex);
notifierMutex = NULL;
/*
* Next, forcibly reset all the "one time" locks used by the threading
* subsystem. This may be necessary on some (all?) variants of Unix
* where the pthread internal state does not survive a call to fork().
*/
TclpResetLocks();
/*
* Next, attempt to free thread specific data for this module, if that
* is necessary.
*/
if (dataKey != (Tcl_ThreadDataKey)0) {
Tcl_UnsetThreadData(&dataKey, sizeof(ThreadSpecificData), 1 /*all */);
dataKey = (Tcl_ThreadDataKey)0;
}
/*
* Force the notifier subsystem to be initialized now. This should
* create the notifier thread in this process. Subsequently, that new
* thread will re-open the trigger pipe.
*/
if (shouldInitNotifier) {
Tcl_InitNotifier();
}
}
#endif /* HAVE_PTHREAD_ATFORK */
+30
View File
@@ -314,6 +314,36 @@ Tcl_GetCurrentThread(void)
#endif
}
/*
*----------------------------------------------------------------------
*
* TclpResetLocks
*
* This procedure is used to forcibly reset all the "one time" locks
* used by the other platform-specific locking procedures. Currently,
* this procedure is only called from the notifier to handle fork()
* and must not be called from anywhere else.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
void
TclpResetLocks(void)
{
#ifdef TCL_THREADS
pthread_mutex_t dummyLock = PTHREAD_MUTEX_INITIALIZER;
memcpy((void *)&masterLock, (void *)&dummyLock, sizeof(pthread_mutex_t));
memcpy((void *)&initLock, (void *)&dummyLock, sizeof(pthread_mutex_t));
memcpy((void *)&allocLock, (void *)&dummyLock, sizeof(pthread_mutex_t));
#endif
}
/*
*----------------------------------------------------------------------
*
+23
View File
@@ -337,6 +337,29 @@ Tcl_GetCurrentThread(void)
return (Tcl_ThreadId) INT2PTR(GetCurrentThreadId());
}
/*
*----------------------------------------------------------------------
*
* TclpResetLocks
*
* This procedure is used to forcibly reset all the "one time" locks
* used by the other platform-specific locking procedures. Currently,
* this procedure does nothing and should not be called.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
void
TclpResetLocks(void)
{
}
/*
*----------------------------------------------------------------------
*