text: /* 
   * tclUnixThrd.c --
   *
   *	This file implements the UNIX-specific thread support.
   *
   * Copyright (c) 1991-1994 The Regents of the University of California.
   * Copyright (c) 1994-1997 Sun Microsystems, Inc.
   *
   * See the file "license.terms" for information on usage and redistribution
   * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
   *
   * SCCS:  @(#) tclUnixThrd.c 1.18 98/02/19 14:24:12
   */
  
  #include "tclInt.h"
  #include "tclPort.h"
  
  #ifdef TCL_THREADS
  
  #include "pthread.h"
  
  typedef struct ThreadSpecificData {
      char nabuf[16];
  } ThreadSpecificData;
  
  static Tcl_ThreadDataKey dataKey;
  
  /*
   * masterLock is used to serialize creation of mutexes, condition
   * variables, and thread local storage.
   * This is the only place that can count on the ability to statically
   * initialize the mutex.
   */
  
  static pthread_mutex_t masterLock = PTHREAD_MUTEX_INITIALIZER;
  
  /*
   * initLock is used to serialize initialization and finalization
   * of Tcl.  It cannot use any dyamically allocated storage.
   */
  
  static pthread_mutex_t initLock = PTHREAD_MUTEX_INITIALIZER;
  
  /*
   * allocLock is used by Tcl's version of malloc for synchronization.
   * For obvious reasons, cannot use any dyamically allocated storage.
   */
  
  static pthread_mutex_t allocLock = PTHREAD_MUTEX_INITIALIZER;
  static pthread_mutex_t *allocLockPtr = &allocLock;
  
  /*
   * These are for the critical sections inside this file.
   */
  
  #define MASTER_LOCK	pthread_mutex_lock(&masterLock)
  #define MASTER_UNLOCK	pthread_mutex_unlock(&masterLock)
  
  #endif /* TCL_THREADS */
  
  
  /*
   *----------------------------------------------------------------------
   *
   * TclpThreadCreate --
   *
   *	This procedure creates a new thread.
   *
   * Results:
   *	TCL_OK if the thread could be created.  The thread ID is
   *	returned in a parameter.
   *
   * Side effects:
   *	A new thread is created.
   *
   *----------------------------------------------------------------------
   */
  
  int
  TclpThreadCreate(idPtr, proc, clientData, stackSize, flags)
      Tcl_ThreadId *idPtr;		/* Return, the ID of the thread */
      Tcl_ThreadCreateProc proc;		/* Main() function of the thread */
      ClientData clientData;		/* The one argument to Main() */
      int stackSize;			/* Size of stack for the new thread */
      int flags;				/* Flags controlling behaviour of
  					 * the new thread */
  {
  #ifdef TCL_THREADS
      pthread_attr_t attr;
      pthread_t theThread;
      int result;
  
      pthread_attr_init(&attr);
      pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
  
  #ifdef HAVE_PTHREAD_ATTR_SETSTACKSIZE
      if (stackSize != TCL_THREAD_STACK_DEFAULT) {
          pthread_attr_setstacksize(&attr, (size_t) stackSize);
  #ifdef TCL_THREAD_STACK_MIN
      } else {
          /*
  	 * Certain systems define a thread stack size that by default is
  	 * too small for many operations.  The user has the option of
  	 * defining TCL_THREAD_STACK_MIN to a value large enough to work
  	 * for their needs.  This would look like (for 128K min stack):
  	 *    make MEM_DEBUG_FLAGS=-DTCL_THREAD_STACK_MIN=131072L
  	 *
  	 * This solution is not optimal, as we should allow the user to
  	 * specify a size at runtime, but we don't want to slow this function
  	 * down, and that would still leave the main thread at the default.
  	 */
  
          size_t size;
  	result = pthread_attr_getstacksize(&attr, &size);
  	if (!result && (size < TCL_THREAD_STACK_MIN)) {
  	    pthread_attr_setstacksize(&attr, (size_t) TCL_THREAD_STACK_MIN);
  	}
  #endif
      }
  #endif
      if (! (flags & TCL_THREAD_JOINABLE)) {
          pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
      }
  
  
      if (pthread_create(&theThread, &attr,
  	    (void * (*)())proc, (void *)clientData) &&
  	    pthread_create(&theThread, NULL,
  		    (void * (*)())proc, (void *)clientData)) {
  	result = TCL_ERROR;
      } else {
  	*idPtr = (Tcl_ThreadId)theThread;
  	result = TCL_OK;
      }
      pthread_attr_destroy(&attr);
      return result;
  #else
      return TCL_ERROR;
  #endif /* TCL_THREADS */
  }
  
  /*
   *----------------------------------------------------------------------
   *
   * Tcl_JoinThread --
   *
   *	This procedure waits upon the exit of the specified thread.
   *
   * Results:
   *	TCL_OK if the wait was successful, TCL_ERROR else.
   *
   * Side effects:
   *	The result area is set to the exit code of the thread we
   *	waited upon.
   *
   *----------------------------------------------------------------------
   */
  
  int
  Tcl_JoinThread(threadId, state)
      Tcl_ThreadId threadId; /* Id of the thread to wait upon */
      int*     state;	   /* Reference to the storage the result
  			    * of the thread we wait upon will be
  			    * written into. */
  {
  #ifdef TCL_THREADS
      int result;
      unsigned long retcode;
  
      result = pthread_join((pthread_t) threadId, (void**) &retcode);
      if (state) {
  	*state = (int) retcode;
      }
      return (result == 0) ? TCL_OK : TCL_ERROR;
  #else
      return TCL_ERROR;
  #endif
  }
  
  #ifdef TCL_THREADS
  /*
   *----------------------------------------------------------------------
   *
   * TclpThreadExit --
   *
   *	This procedure terminates the current thread.
   *
   * Results:
   *	None.
   *
   * Side effects:
   *	This procedure terminates the current thread.
   *
   *----------------------------------------------------------------------
   */
  
  void
  TclpThreadExit(status)
      int status;
  {
      pthread_exit((VOID *)status);
  }
  #endif /* TCL_THREADS */
  
  /*
   *----------------------------------------------------------------------
   *
   * Tcl_GetCurrentThread --
   *
   *	This procedure returns the ID of the currently running thread.
   *
   * Results:
   *	A thread ID.
   *
   * Side effects:
   *	None.
   *
   *----------------------------------------------------------------------
   */
  
  Tcl_ThreadId
  Tcl_GetCurrentThread()
  {
  #ifdef TCL_THREADS
      return (Tcl_ThreadId) pthread_self();
  #else
      return (Tcl_ThreadId) 0;
  #endif
  }
  
  
  /*
   *----------------------------------------------------------------------
   *
   * TclpInitLock
   *
   *	This procedure is used to grab a lock that serializes initialization
   *	and finalization of Tcl.  On some platforms this may also initialize
   *	the mutex used to serialize creation of more mutexes and thread
   *	local storage keys.
   *
   * Results:
   *	None.
   *
   * Side effects:
   *	Acquire the initialization mutex.
   *
   *----------------------------------------------------------------------
   */
  
  void
  TclpInitLock()
  {
  #ifdef TCL_THREADS
      pthread_mutex_lock(&initLock);
  #endif
  }
  
  /*
   *----------------------------------------------------------------------
   *
   * TclpFinalizeLock
   *
   *	This procedure is used to destroy all private resources used in
   *	this file.
   *
   * Results:
   *	None.
   *
   * Side effects:
   *	Destroys everything private.  TclpInitLock must be held
   *	entering this function.
   *
   *----------------------------------------------------------------------
   */
  
  void
  TclFinalizeLock ()
  {
  #ifdef TCL_THREADS
      /*
       * You do not need to destroy mutexes that were created with the
       * PTHREAD_MUTEX_INITIALIZER macro.  These mutexes do not need
       * any destruction: masterLock, allocLock, and initLock.
       */
      pthread_mutex_unlock(&initLock);
  #endif
  }
  
  /*
   *----------------------------------------------------------------------
   *
   * TclpInitUnlock
   *
   *	This procedure is used to release a lock that serializes initialization
   *	and finalization of Tcl.
   *
   * Results:
   *	None.
   *
   * Side effects:
   *	Release the initialization mutex.
   *
   *----------------------------------------------------------------------
   */
  
  void
  TclpInitUnlock()
  {
  #ifdef TCL_THREADS
      pthread_mutex_unlock(&initLock);
  #endif
  }
  
  /*
   *----------------------------------------------------------------------
   *
   * TclpMasterLock
   *
   *	This procedure is used to grab a lock that serializes creation
   *	and finalization of serialization objects.  This interface is
   *	only needed in finalization; it is hidden during
   *	creation of the objects.
   *
   *	This lock must be different than the initLock because the
   *	initLock is held during creation of syncronization objects.
   *
   * Results:
   *	None.
   *
   * Side effects:
   *	Acquire the master mutex.
   *
   *----------------------------------------------------------------------
   */
  
  void
  TclpMasterLock()
  {
  #ifdef TCL_THREADS
      pthread_mutex_lock(&masterLock);
  #endif
  }
  
  
  /*
   *----------------------------------------------------------------------
   *
   * TclpMasterUnlock
   *
   *	This procedure is used to release a lock that serializes creation
   *	and finalization of synchronization objects.
   *
   * Results:
   *	None.
   *
   * Side effects:
   *	Release the master mutex.
   *
   *----------------------------------------------------------------------
   */
  
  void
  TclpMasterUnlock()
  {
  #ifdef TCL_THREADS
      pthread_mutex_unlock(&masterLock);
  #endif
  }
  
  
  /*
   *----------------------------------------------------------------------
   *
   * Tcl_GetAllocMutex
   *
   *	This procedure returns a pointer to a statically initialized
   *	mutex for use by the memory allocator.  The alloctor must
   *	use this lock, because all other locks are allocated...
   *
   * Results:
   *	A pointer to a mutex that is suitable for passing to
   *	Tcl_MutexLock and Tcl_MutexUnlock.
   *
   * Side effects:
   *	None.
   *
   *----------------------------------------------------------------------
   */
  
  Tcl_Mutex *
  Tcl_GetAllocMutex()
  {
  #ifdef TCL_THREADS
      return (Tcl_Mutex *)&allocLockPtr;
  #else
      return NULL;
  #endif
  }
  
  #ifdef TCL_THREADS
  
  /*
   *----------------------------------------------------------------------
   *
   * Tcl_MutexLock --
   *
   *	This procedure is invoked to lock a mutex.  This procedure
   *	handles initializing the mutex, if necessary.  The caller
   *	can rely on the fact that Tcl_Mutex is an opaque pointer.
   *	This routine will change that pointer from NULL after first use.
   *
   * Results:
   *	None.
   *
   * Side effects:
   *	May block the current thread.  The mutex is aquired when
   *	this returns.  Will allocate memory for a pthread_mutex_t
   *	and initialize this the first time this Tcl_Mutex is used.
   *
   *----------------------------------------------------------------------
   */
  
  void
  Tcl_MutexLock(mutexPtr)
      Tcl_Mutex *mutexPtr;	/* Really (pthread_mutex_t **) */
  {
      pthread_mutex_t *pmutexPtr;
      if (*mutexPtr == NULL) {
  	MASTER_LOCK;
  	if (*mutexPtr == NULL) {
  	    /* 
  	     * Double inside master lock check to avoid a race condition.
  	     */
      
  	    pmutexPtr = (pthread_mutex_t *)ckalloc(sizeof(pthread_mutex_t));
  	    pthread_mutex_init(pmutexPtr, NULL);
  	    *mutexPtr = (Tcl_Mutex)pmutexPtr;
  	    TclRememberMutex(mutexPtr);
  	}
  	MASTER_UNLOCK;
      }
      pmutexPtr = *((pthread_mutex_t **)mutexPtr);
      pthread_mutex_lock(pmutexPtr);
  }
  
  
  /*
   *----------------------------------------------------------------------
   *
   * Tcl_MutexUnlock --
   *
   *	This procedure is invoked to unlock a mutex.  The mutex must
   *	have been locked by Tcl_MutexLock.
   *
   * Results:
   *	None.
   *
   * Side effects:
   *	The mutex is released when this returns.
   *
   *----------------------------------------------------------------------
   */
  
  void
  Tcl_MutexUnlock(mutexPtr)
      Tcl_Mutex *mutexPtr;	/* Really (pthread_mutex_t **) */
  {
      pthread_mutex_t *pmutexPtr = *(pthread_mutex_t **)mutexPtr;
      pthread_mutex_unlock(pmutexPtr);
  }
  
  
  /*
   *----------------------------------------------------------------------
   *
   * TclpFinalizeMutex --
   *
   *	This procedure is invoked to clean up one mutex.  This is only
   *	safe to call at the end of time.
   *
   *	This assumes the Master Lock is held.
   *
   * Results:
   *	None.
   *
   * Side effects:
   *	The mutex list is deallocated.
   *
   *----------------------------------------------------------------------
   */
  
  void
  TclpFinalizeMutex(mutexPtr)
      Tcl_Mutex *mutexPtr;
  {
      pthread_mutex_t *pmutexPtr = *(pthread_mutex_t **)mutexPtr;
      if (pmutexPtr != NULL) {
          pthread_mutex_destroy(pmutexPtr);
  	ckfree((char *)pmutexPtr);
  	*mutexPtr = NULL;
      }
  }
  
  
  /*
   *----------------------------------------------------------------------
   *
   * TclpThreadDataKeyInit --
   *
   *	This procedure initializes a thread specific data block key.
   *	Each thread has table of pointers to thread specific data.
   *	all threads agree on which table entry is used by each module.
   *	this is remembered in a "data key", that is just an index into
   *	this table.  To allow self initialization, the interface
   *	passes a pointer to this key and the first thread to use
   *	the key fills in the pointer to the key.  The key should be
   *	a process-wide static.
   *
   * Results:
   *	None.
   *
   * Side effects:
   *	Will allocate memory the first time this process calls for
   *	this key.  In this case it modifies its argument
   *	to hold the pointer to information about the key.
   *
   *----------------------------------------------------------------------
   */
  
  void
  TclpThreadDataKeyInit(keyPtr)
      Tcl_ThreadDataKey *keyPtr;	/* Identifier for the data chunk,
  				 * really (pthread_key_t **) */
  {
      pthread_key_t *pkeyPtr;
  
      MASTER_LOCK;
      if (*keyPtr == NULL) {
  	pkeyPtr = (pthread_key_t *)ckalloc(sizeof(pthread_key_t));
  	pthread_key_create(pkeyPtr, NULL);
  	*keyPtr = (Tcl_ThreadDataKey)pkeyPtr;
  	TclRememberDataKey(keyPtr);
      }
      MASTER_UNLOCK;
  }
  
  /*
   *----------------------------------------------------------------------
   *
   * TclpThreadDataKeyGet --
   *
   *	This procedure returns a pointer to a block of thread local storage.
   *
   * Results:
   *	A thread-specific pointer to the data structure, or NULL
   *	if the memory has not been assigned to this key for this thread.
   *
   * Side effects:
   *	None.
   *
   *----------------------------------------------------------------------
   */
  
  VOID *
  TclpThreadDataKeyGet(keyPtr)
      Tcl_ThreadDataKey *keyPtr;	/* Identifier for the data chunk,
  				 * really (pthread_key_t **) */
  {
      pthread_key_t *pkeyPtr = *(pthread_key_t **)keyPtr;
      if (pkeyPtr == NULL) {
  	return NULL;
      } else {
  	return (VOID *)pthread_getspecific(*pkeyPtr);
      }
  }
  
  
  /*
   *----------------------------------------------------------------------
   *
   * TclpThreadDataKeySet --
   *
   *	This procedure sets the pointer to a block of thread local storage.
   *
   * Results:
   *	None.
   *
   * Side effects:
   *	Sets up the thread so future calls to TclpThreadDataKeyGet with
   *	this key will return the data pointer.
   *
   *----------------------------------------------------------------------
   */
  
  void
  TclpThreadDataKeySet(keyPtr, data)
      Tcl_ThreadDataKey *keyPtr;	/* Identifier for the data chunk,
  				 * really (pthread_key_t **) */
      VOID *data;			/* Thread local storage */
  {
      pthread_key_t *pkeyPtr = *(pthread_key_t **)keyPtr;
      pthread_setspecific(*pkeyPtr, data);
  }
  
  /*
   *----------------------------------------------------------------------
   *
   * TclpFinalizeThreadData --
   *
   *	This procedure cleans up the thread-local storage.  This is
   *	called once for each thread.
   *
   * Results:
   *	None.
   *
   * Side effects:
   *	Frees up all thread local storage.
   *
   *----------------------------------------------------------------------
   */
  
  void
  TclpFinalizeThreadData(keyPtr)
      Tcl_ThreadDataKey *keyPtr;
  {
      VOID *result;
      pthread_key_t *pkeyPtr;
  
      if (*keyPtr != NULL) {
  	pkeyPtr = *(pthread_key_t **)keyPtr;
  	result = (VOID *)pthread_getspecific(*pkeyPtr);
  	if (result != NULL) {
  	    ckfree((char *)result);
  	    pthread_setspecific(*pkeyPtr, (void *)NULL);
  	}
      }
  }
  
  /*
   *----------------------------------------------------------------------
   *
   * TclpFinalizeThreadDataKey --
   *
   *	This procedure is invoked to clean up one key.  This is a
   *	process-wide storage identifier.  The thread finalization code
   *	cleans up the thread local storage itself.
   *
   *	This assumes the master lock is held.
   *
   * Results:
   *	None.
   *
   * Side effects:
   *	The key is deallocated.
   *
   *----------------------------------------------------------------------
   */
  
  void
  TclpFinalizeThreadDataKey(keyPtr)
      Tcl_ThreadDataKey *keyPtr;
  {
      pthread_key_t *pkeyPtr;
      if (*keyPtr != NULL) {
  	pkeyPtr = *(pthread_key_t **)keyPtr;
  	pthread_key_delete(*pkeyPtr);
  	ckfree((char *)pkeyPtr);
  	*keyPtr = NULL;
      }
  }
  
  
  /*
   *----------------------------------------------------------------------
   *
   * Tcl_ConditionWait --
   *
   *	This procedure is invoked to wait on a condition variable.
   *	The mutex is automically released as part of the wait, and
   *	automatically grabbed when the condition is signaled.
   *
   *	The mutex must be held when this procedure is called.
   *
   * Results:
   *	None.
   *
   * Side effects:
   *	May block the current thread.  The mutex is aquired when
   *	this returns.  Will allocate memory for a pthread_mutex_t
   *	and initialize this the first time this Tcl_Mutex is used.
   *
   *----------------------------------------------------------------------
   */
  
  void
  Tcl_ConditionWait(condPtr, mutexPtr, timePtr)
      Tcl_Condition *condPtr;	/* Really (pthread_cond_t **) */
      Tcl_Mutex *mutexPtr;	/* Really (pthread_mutex_t **) */
      Tcl_Time *timePtr;		/* Timeout on waiting period */
  {
      pthread_cond_t *pcondPtr;
      pthread_mutex_t *pmutexPtr;
      struct timespec ptime;
  
      if (*condPtr == NULL) {
  	MASTER_LOCK;
  
  	/* 
  	 * Double check inside mutex to avoid race,
  	 * then initialize condition variable if necessary.
  	 */
  
  	if (*condPtr == NULL) {
  	    pcondPtr = (pthread_cond_t *)ckalloc(sizeof(pthread_cond_t));
  	    pthread_cond_init(pcondPtr, NULL);
  	    *condPtr = (Tcl_Condition)pcondPtr;
  	    TclRememberCondition(condPtr);
  	}
  	MASTER_UNLOCK;
      }
      pmutexPtr = *((pthread_mutex_t **)mutexPtr);
      pcondPtr = *((pthread_cond_t **)condPtr);
      if (timePtr == NULL) {
  	pthread_cond_wait(pcondPtr, pmutexPtr);
      } else {
  	Tcl_Time now;
  
  	/*
  	 * Make sure to take into account the microsecond component of the
  	 * current time, including possible overflow situations. [Bug #411603]
  	 */
  
  	Tcl_GetTime(&now);
  	ptime.tv_sec = timePtr->sec + now.sec +
  	    (timePtr->usec + now.usec) / 1000000;
  	ptime.tv_nsec = 1000 * ((timePtr->usec + now.usec) % 1000000);
  	pthread_cond_timedwait(pcondPtr, pmutexPtr, &ptime);
      }
  }
  
  /*
   *----------------------------------------------------------------------
   *
   * Tcl_ConditionNotify --
   *
   *	This procedure is invoked to signal a condition variable.
   *
   *	The mutex must be held during this call to avoid races,
   *	but this interface does not enforce that.
   *
   * Results:
   *	None.
   *
   * Side effects:
   *	May unblock another thread.
   *
   *----------------------------------------------------------------------
   */
  
  void
  Tcl_ConditionNotify(condPtr)
      Tcl_Condition *condPtr;
  {
      pthread_cond_t *pcondPtr = *((pthread_cond_t **)condPtr);
      if (pcondPtr != NULL) {
  	pthread_cond_broadcast(pcondPtr);
      } else {
  	/*
  	 * Noone has used the condition variable, so there are no waiters.
  	 */
      }
  }
  
  
  /*
   *----------------------------------------------------------------------
   *
   * TclpFinalizeCondition --
   *
   *	This procedure is invoked to clean up a condition variable.
   *	This is only safe to call at the end of time.
   *
   *	This assumes the Master Lock is held.
   *
   * Results:
   *	None.
   *
   * Side effects:
   *	The condition variable is deallocated.
   *
   *----------------------------------------------------------------------
   */
  
  void
  TclpFinalizeCondition(condPtr)
      Tcl_Condition *condPtr;
  {
      pthread_cond_t *pcondPtr = *(pthread_cond_t **)condPtr;
      if (pcondPtr != NULL) {
  	pthread_cond_destroy(pcondPtr);
  	ckfree((char *)pcondPtr);
  	*condPtr = NULL;
      }
  }
  #endif /* TCL_THREADS */
  
  /*
   *----------------------------------------------------------------------
   *
   * TclpReaddir, TclpLocaltime, TclpGmtime, TclpInetNtoa --
   *
   *	These procedures replace core C versions to be used in a
   *	threaded environment.
   *
   * Results:
   *	See documentation of C functions.
   *
   * Side effects:
   *	See documentation of C functions.
   *
   * Notes:
   * 	TclpReaddir is no longer used by the core (see 1095909),
   * 	but it appears in the internal stubs table (see #589526).
   *----------------------------------------------------------------------
   */
  
  Tcl_DirEntry *
  TclpReaddir(DIR * dir)
  {
      return TclOSreaddir(dir);
  }
  
  char *
  TclpInetNtoa(struct in_addr addr)
  {
  #ifdef TCL_THREADS
      ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
      unsigned char *b = (unsigned char*) &addr.s_addr;
  
      sprintf(tsdPtr->nabuf, "%u.%u.%u.%u", b[0], b[1], b[2], b[3]);
      return tsdPtr->nabuf;
  #else
      return inet_ntoa(addr);
  #endif
  }
  
  #if defined(TCL_THREADS) && defined(USE_THREAD_ALLOC) && !defined(TCL_MEM_DEBUG)
  /*
   * Additions by AOL for specialized thread memory allocator.
   */
  #ifdef USE_THREAD_ALLOC
  static volatile int initialized = 0;
  static pthread_key_t	key;
  
  typedef struct allocMutex {
      Tcl_Mutex       tlock;
      pthread_mutex_t plock;
  } allocMutex;
  
  Tcl_Mutex *
  TclpNewAllocMutex(void)
  {
      struct allocMutex *lockPtr;
  
      lockPtr = malloc(sizeof(struct allocMutex));
      if (lockPtr == NULL) {
  	panic("could not allocate lock");
      }
      lockPtr->tlock = (Tcl_Mutex) &lockPtr->plock;
      pthread_mutex_init(&lockPtr->plock, NULL);
      return &lockPtr->tlock;
  }
  
  void
  TclpFreeAllocMutex(mutex)
      Tcl_Mutex *mutex; /* The alloc mutex to free. */
  {
      allocMutex* lockPtr = (allocMutex*) mutex;
      if (!lockPtr) return;
      pthread_mutex_destroy(&lockPtr->plock);
      free(lockPtr);
  }
  
  void TclpFreeAllocCache(ptr)
      void *ptr;
  {
      if (ptr != NULL) {
          /*
           * Called by the pthread lib when a thread exits
           */
          TclFreeAllocCache(ptr);
      } else if (initialized) {
          /*
           * Called by us in TclFinalizeThreadAlloc() during
           * the library finalization initiated from Tcl_Finalize()
           */
          pthread_key_delete(key);
          initialized = 0;
      }
  }
  
  void *
  TclpGetAllocCache(void)
  {
      if (!initialized) {
  	pthread_mutex_lock(allocLockPtr);
  	if (!initialized) {
  	    pthread_key_create(&key, TclpFreeAllocCache);
  	    initialized = 1;
  	}
  	pthread_mutex_unlock(allocLockPtr);
      }
      return pthread_getspecific(key);
  }
  
  void
  TclpSetAllocCache(void *arg)
  {
      pthread_setspecific(key, arg);
  }
  
  #endif /* USE_THREAD_ALLOC */
  #endif /* TCL_THREADS */
  