tclThreadStorage.c

Go to the documentation of this file.
00001 /*
00002  * tclThreadStorage.c --
00003  *
00004  *      This file implements platform independent thread storage operations.
00005  *
00006  * Copyright (c) 2003-2004 by Joe Mistachkin
00007  *
00008  * See the file "license.terms" for information on usage and redistribution of
00009  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
00010  *
00011  * RCS: @(#) $Id: tclThreadStorage.c,v 1.15 2007/12/13 15:23:20 dgp Exp $
00012  */
00013 
00014 #include "tclInt.h"
00015 
00016 #if defined(TCL_THREADS)
00017 
00018 /*
00019  * This is the thread storage cache array and it's accompanying mutex. The
00020  * elements are pairs of thread Id and an associated hash table pointer; the
00021  * hash table being pointed to contains the thread storage for it's associated
00022  * thread. The purpose of this cache is to minimize the number of hash table
00023  * lookups in the master thread storage hash table.
00024  */
00025 
00026 static Tcl_Mutex threadStorageLock;
00027 
00028 /*
00029  * This is the struct used for a thread storage cache slot. It contains the
00030  * owning thread Id and the associated hash table pointer.
00031  */
00032 
00033 typedef struct ThreadStorage {
00034     Tcl_ThreadId id;            /* the owning thread id */
00035     Tcl_HashTable *hashTablePtr;/* the hash table for the thread */
00036 } ThreadStorage;
00037 
00038 /*
00039  * These are the prototypes for the custom hash table allocation functions
00040  * used by the thread storage subsystem.
00041  */
00042 
00043 static Tcl_HashEntry *  AllocThreadStorageEntry(Tcl_HashTable *tablePtr,
00044                             void *keyPtr);
00045 static void             FreeThreadStorageEntry(Tcl_HashEntry *hPtr);
00046 static Tcl_HashTable *  ThreadStorageGetHashTable(Tcl_ThreadId id);
00047 
00048 /*
00049  * This is the hash key type for thread storage. We MUST use this in
00050  * combination with the new hash key type flag TCL_HASH_KEY_SYSTEM_HASH
00051  * because these hash tables MAY be used by the threaded memory allocator.
00052  */
00053 
00054 static Tcl_HashKeyType tclThreadStorageHashKeyType = {
00055     TCL_HASH_KEY_TYPE_VERSION,          /* version */
00056     TCL_HASH_KEY_SYSTEM_HASH | TCL_HASH_KEY_RANDOMIZE_HASH,
00057                                         /* flags */
00058     NULL,                               /* hashKeyProc */
00059     NULL,                               /* compareKeysProc */
00060     AllocThreadStorageEntry,            /* allocEntryProc */
00061     FreeThreadStorageEntry              /* freeEntryProc */
00062 };
00063 
00064 /*
00065  * This is an invalid thread value.
00066  */
00067 
00068 #define STORAGE_INVALID_THREAD  (Tcl_ThreadId)0
00069 
00070 /*
00071  * This is the value for an invalid thread storage key.
00072  */
00073 
00074 #define STORAGE_INVALID_KEY     0
00075 
00076 /*
00077  * This is the first valid key for use by external callers. All the values
00078  * below this are RESERVED for future use.
00079  */
00080 
00081 #define STORAGE_FIRST_KEY       1
00082 
00083 /*
00084  * This is the default number of thread storage cache slots. This define may
00085  * need to be fine tuned for maximum performance.
00086  */
00087 
00088 #define STORAGE_CACHE_SLOTS     97
00089 
00090 /*
00091  * This is the master thread storage hash table. It is keyed on thread Id and
00092  * contains values that are hash tables for each thread. The thread specific
00093  * hash tables contain the actual thread storage.
00094  */
00095 
00096 static Tcl_HashTable threadStorageHashTable;
00097 
00098 /*
00099  * This is the next thread data key value to use. We increment this everytime
00100  * we "allocate" one. It is initially set to 1 in TclInitThreadStorage.
00101  */
00102 
00103 static int nextThreadStorageKey = STORAGE_INVALID_KEY;
00104 
00105 /*
00106  * This is the master thread storage cache. Per Kevin Kenny's idea, this
00107  * prevents unnecessary lookups for threads that use a lot of thread storage.
00108  */
00109 
00110 static volatile ThreadStorage threadStorageCache[STORAGE_CACHE_SLOTS];
00111 
00112 /*
00113  *----------------------------------------------------------------------
00114  *
00115  * AllocThreadStorageEntry --
00116  *
00117  *      Allocate space for a Tcl_HashEntry using TclpSysAlloc (not ckalloc).
00118  *      We do this because the threaded memory allocator MAY use the thread
00119  *      storage hash tables.
00120  *
00121  * Results:
00122  *      The return value is a pointer to the created entry.
00123  *
00124  * Side effects:
00125  *      None.
00126  *
00127  *----------------------------------------------------------------------
00128  */
00129 
00130 static Tcl_HashEntry *
00131 AllocThreadStorageEntry(
00132     Tcl_HashTable *tablePtr,    /* Hash table. */
00133     void *keyPtr)               /* Key to store in the hash table entry. */
00134 {
00135     Tcl_HashEntry *hPtr;
00136 
00137     hPtr = (Tcl_HashEntry *) TclpSysAlloc(sizeof(Tcl_HashEntry), 0);
00138     hPtr->key.oneWordValue = keyPtr;
00139     hPtr->clientData = NULL;
00140     
00141     return hPtr;
00142 }
00143 
00144 /*
00145  *----------------------------------------------------------------------
00146  *
00147  * FreeThreadStorageEntry --
00148  *
00149  *      Frees space for a Tcl_HashEntry using TclpSysFree (not ckfree). We do
00150  *      this because the threaded memory allocator MAY use the thread storage
00151  *      hash tables.
00152  *
00153  * Results:
00154  *      None.
00155  *
00156  * Side effects:
00157  *      None.
00158  *
00159  *----------------------------------------------------------------------
00160  */
00161 
00162 static void
00163 FreeThreadStorageEntry(
00164     Tcl_HashEntry *hPtr)        /* Hash entry to free. */
00165 {
00166     TclpSysFree((char *) hPtr);
00167 }
00168 
00169 /*
00170  *----------------------------------------------------------------------
00171  *
00172  * ThreadStorageGetHashTable --
00173  *
00174  *      This procedure returns a hash table pointer to be used for thread
00175  *      storage for the specified thread. This assumes that thread storage
00176  *      lock is held.
00177  *
00178  * Results:
00179  *      A hash table pointer for the specified thread, or NULL if the hash
00180  *      table has not been created yet.
00181  *
00182  * Side effects:
00183  *      May change an entry in the master thread storage cache to point to the
00184  *      specified thread and it's associated hash table.
00185  *
00186  *----------------------------------------------------------------------
00187  */
00188 
00189 static Tcl_HashTable *
00190 ThreadStorageGetHashTable(
00191     Tcl_ThreadId id)            /* Id of thread to get hash table for */
00192 {
00193     int index = PTR2UINT(id) % STORAGE_CACHE_SLOTS;
00194     Tcl_HashEntry *hPtr;
00195     int isNew;
00196 
00197     /*
00198      * It's important that we pick up the hash table pointer BEFORE comparing
00199      * thread Id in case another thread is in the critical region changing
00200      * things out from under you.
00201      */
00202 
00203     Tcl_HashTable *hashTablePtr = threadStorageCache[index].hashTablePtr;
00204 
00205     if (threadStorageCache[index].id != id) {
00206         Tcl_MutexLock(&threadStorageLock);
00207 
00208         /*
00209          * It's not in the cache, so we look it up...
00210          */
00211 
00212         hPtr = Tcl_FindHashEntry(&threadStorageHashTable, (char *) id);
00213 
00214         if (hPtr != NULL) {
00215             /*
00216              * We found it, extract the hash table pointer.
00217              */
00218 
00219             hashTablePtr = Tcl_GetHashValue(hPtr);
00220         } else {
00221             /*
00222              * The thread specific hash table is not found.
00223              */
00224 
00225             hashTablePtr = NULL;
00226         }
00227 
00228         if (hashTablePtr == NULL) {
00229             hashTablePtr = (Tcl_HashTable *)
00230                     TclpSysAlloc(sizeof(Tcl_HashTable), 0);
00231 
00232             if (hashTablePtr == NULL) {
00233                 Tcl_Panic("could not allocate thread specific hash table, "
00234                         "TclpSysAlloc failed from ThreadStorageGetHashTable!");
00235             }
00236             Tcl_InitCustomHashTable(hashTablePtr, TCL_CUSTOM_TYPE_KEYS,
00237                     &tclThreadStorageHashKeyType);
00238 
00239             /*
00240              * Add new thread storage hash table to the master hash table.
00241              */
00242 
00243             hPtr = Tcl_CreateHashEntry(&threadStorageHashTable, (char *) id,
00244                     &isNew);
00245 
00246             if (hPtr == NULL) {
00247                 Tcl_Panic("Tcl_CreateHashEntry failed from "
00248                         "ThreadStorageGetHashTable!");
00249             }
00250             Tcl_SetHashValue(hPtr, hashTablePtr);
00251         }
00252 
00253         /*
00254          * Now, we put it in the cache since it is highly likely it will be
00255          * needed again shortly.
00256          */
00257 
00258         threadStorageCache[index].id = id;
00259         threadStorageCache[index].hashTablePtr = hashTablePtr;
00260 
00261         Tcl_MutexUnlock(&threadStorageLock);
00262     }
00263 
00264     return hashTablePtr;
00265 }
00266 
00267 /*
00268  *----------------------------------------------------------------------
00269  *
00270  * TclInitThreadStorage --
00271  *
00272  *      Initializes the thread storage allocator.
00273  *
00274  * Results:
00275  *      None.
00276  *
00277  * Side effects:
00278  *      This procedure initializes the master hash table that maps thread ID
00279  *      onto the individual index tables that map thread data key to thread
00280  *      data. It also creates a cache that enables fast lookup of the thread
00281  *      data block array for a recently executing thread without using
00282  *      spinlocks.
00283  *
00284  * This procedure is called from an extremely early point in Tcl's
00285  * initialization. In particular, it may not use ckalloc/ckfree because they
00286  * may depend on thread-local storage (it uses TclpSysAlloc and TclpSysFree
00287  * instead). It may not depend on synchronization primitives - but no threads
00288  * other than the master thread have yet been launched.
00289  *
00290  *----------------------------------------------------------------------
00291  */
00292 
00293 void
00294 TclInitThreadStorage(void)
00295 {
00296     Tcl_InitCustomHashTable(&threadStorageHashTable, TCL_CUSTOM_TYPE_KEYS,
00297             &tclThreadStorageHashKeyType);
00298 
00299     /*
00300      * We also initialize the cache.
00301      */
00302 
00303     memset((void*) &threadStorageCache, 0,
00304             sizeof(ThreadStorage) * STORAGE_CACHE_SLOTS);
00305 
00306     /*
00307      * Now, we set the first value to be used for a thread data key.
00308      */
00309 
00310     nextThreadStorageKey = STORAGE_FIRST_KEY;
00311 }
00312 
00313 /*
00314  *----------------------------------------------------------------------
00315  *
00316  * TclpThreadDataKeyGet --
00317  *
00318  *      This procedure returns a pointer to a block of thread local storage.
00319  *
00320  * Results:
00321  *      A thread-specific pointer to the data structure, or NULL if the memory
00322  *      has not been assigned to this key for this thread.
00323  *
00324  * Side effects:
00325  *      None.
00326  *
00327  *----------------------------------------------------------------------
00328  */
00329 
00330 void *
00331 TclpThreadDataKeyGet(
00332     Tcl_ThreadDataKey *keyPtr)  /* Identifier for the data chunk, really
00333                                  * (int**) */
00334 {
00335     Tcl_HashTable *hashTablePtr =
00336             ThreadStorageGetHashTable(Tcl_GetCurrentThread());
00337     Tcl_HashEntry *hPtr = Tcl_FindHashEntry(hashTablePtr, (char *) keyPtr);
00338 
00339     if (hPtr == NULL) {
00340         return NULL;
00341     }
00342     return Tcl_GetHashValue(hPtr);
00343 }
00344 
00345 /*
00346  *----------------------------------------------------------------------
00347  *
00348  * TclpThreadDataKeySet --
00349  *
00350  *      This procedure sets the pointer to a block of thread local storage.
00351  *
00352  * Results:
00353  *      None.
00354  *
00355  * Side effects:
00356  *      Sets up the thread so future calls to TclpThreadDataKeyGet with this
00357  *      key will return the data pointer.
00358  *
00359  *----------------------------------------------------------------------
00360  */
00361 
00362 void
00363 TclpThreadDataKeySet(
00364     Tcl_ThreadDataKey *keyPtr,  /* Identifier for the data chunk, really
00365                                  * (pthread_key_t **) */
00366     void *data)                 /* Thread local storage */
00367 {
00368     Tcl_HashTable *hashTablePtr;
00369     Tcl_HashEntry *hPtr;
00370     int dummy;
00371 
00372     hashTablePtr = ThreadStorageGetHashTable(Tcl_GetCurrentThread());
00373     hPtr = Tcl_CreateHashEntry(hashTablePtr, (char *)keyPtr, &dummy);
00374 
00375     Tcl_SetHashValue(hPtr, data);
00376 }
00377 
00378 /*
00379  *----------------------------------------------------------------------
00380  *
00381  * TclpFinalizeThreadDataThread --
00382  *
00383  *      This procedure cleans up the thread storage hash table for the
00384  *      current thread.
00385  *
00386  * Results:
00387  *      None.
00388  *
00389  * Side effects:
00390  *      Frees all associated thread storage, all hash table entries for
00391  *      the thread's thread storage, and the hash table itself.
00392  *
00393  *----------------------------------------------------------------------
00394  */
00395 
00396 void
00397 TclpFinalizeThreadDataThread(void)
00398 {
00399     Tcl_ThreadId id = Tcl_GetCurrentThread();
00400                                 /* Id of the thread to finalize. */
00401     int index = PTR2UINT(id) % STORAGE_CACHE_SLOTS;
00402     Tcl_HashEntry *hPtr;        /* Hash entry for current thread in master
00403                                  * table. */
00404     Tcl_HashTable* hashTablePtr;/* Pointer to the hash table holding TSD
00405                                  * blocks for the current thread*/
00406     Tcl_HashSearch search;      /* Search object to walk the TSD blocks in the
00407                                  * designated thread */
00408     Tcl_HashEntry *hPtr2;       /* Hash entry for a TSD block in the
00409                                  * designated thread. */
00410 
00411     Tcl_MutexLock(&threadStorageLock);
00412     hPtr = Tcl_FindHashEntry(&threadStorageHashTable, (char*)id);
00413     if (hPtr == NULL) {
00414         hashTablePtr = NULL;
00415     } else {
00416         /*
00417          * We found it, extract the hash table pointer.
00418          */
00419 
00420         hashTablePtr = Tcl_GetHashValue(hPtr);
00421         Tcl_DeleteHashEntry(hPtr);
00422 
00423         /*
00424          * Make sure cache entry for this thread is NULL.
00425          */
00426 
00427         if (threadStorageCache[index].id == id) {
00428             /*
00429              * We do not step on another thread's cache entry. This is
00430              * especially important if we are creating and exiting a lot of
00431              * threads.
00432              */
00433 
00434             threadStorageCache[index].id = STORAGE_INVALID_THREAD;
00435             threadStorageCache[index].hashTablePtr = NULL;
00436         }
00437     }
00438     Tcl_MutexUnlock(&threadStorageLock);
00439 
00440     /*
00441      * The thread's hash table has been extracted and removed from the master
00442      * hash table. Now clean up the thread.
00443      */
00444 
00445     if (hashTablePtr != NULL) {
00446         /*
00447          * Free all TSD
00448          */
00449 
00450         for (hPtr2 = Tcl_FirstHashEntry(hashTablePtr, &search); hPtr2 != NULL;
00451                 hPtr2 = Tcl_NextHashEntry(&search)) {
00452             void *blockPtr = Tcl_GetHashValue(hPtr2);
00453 
00454             if (blockPtr != NULL) {
00455                 /*
00456                  * The block itself was allocated in Tcl_GetThreadData using
00457                  * ckalloc; use ckfree to dispose of it.
00458                  */
00459 
00460                 ckfree(blockPtr);
00461             }
00462         }
00463 
00464         /*
00465          * Delete thread specific hash table and free the struct.
00466          */
00467 
00468         Tcl_DeleteHashTable(hashTablePtr);
00469         TclpSysFree((char *) hashTablePtr);
00470     }
00471 }
00472 
00473 /*
00474  *----------------------------------------------------------------------
00475  *
00476  * TclFinalizeThreadStorage --
00477  *
00478  *      This procedure cleans up the master thread storage hash table, all
00479  *      thread specific hash tables, and the thread storage cache.
00480  *
00481  * Results:
00482  *      None.
00483  *
00484  * Side effects:
00485  *      The master thread storage hash table and thread storage cache are
00486  *      reset to their initial (empty) state.
00487  *
00488  *----------------------------------------------------------------------
00489  */
00490 
00491 void
00492 TclFinalizeThreadStorage(void)
00493 {
00494     Tcl_HashSearch search;      /* We need to hit every thread with this
00495                                  * search. */
00496     Tcl_HashEntry *hPtr;        /* Hash entry for current thread in master
00497                                  * table. */
00498     Tcl_MutexLock(&threadStorageLock);
00499 
00500     /*
00501      * We are going to delete the hash table for every thread now. This hash
00502      * table should be empty at this point, except for one entry for the
00503      * current thread.
00504      */
00505 
00506     for (hPtr = Tcl_FirstHashEntry(&threadStorageHashTable, &search);
00507             hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
00508         Tcl_HashTable *hashTablePtr = Tcl_GetHashValue(hPtr);
00509 
00510         if (hashTablePtr != NULL) {
00511             /*
00512              * Delete thread specific hash table for the thread in question
00513              * and free the struct.
00514              */
00515 
00516             Tcl_DeleteHashTable(hashTablePtr);
00517             TclpSysFree((char *)hashTablePtr);
00518         }
00519 
00520         /*
00521          * Delete thread specific entry from master hash table.
00522          */
00523 
00524         Tcl_SetHashValue(hPtr, NULL);
00525     }
00526 
00527     Tcl_DeleteHashTable(&threadStorageHashTable);
00528 
00529     /*
00530      * Clear out the thread storage cache as well.
00531      */
00532 
00533     memset((void*) &threadStorageCache, 0,
00534             sizeof(ThreadStorage) * STORAGE_CACHE_SLOTS);
00535 
00536     /*
00537      * Reset this to zero, it will be set to STORAGE_FIRST_KEY if the thread
00538      * storage subsystem gets reinitialized
00539      */
00540 
00541     nextThreadStorageKey = STORAGE_INVALID_KEY;
00542 
00543     Tcl_MutexUnlock(&threadStorageLock);
00544 }
00545 
00546 #else /* !defined(TCL_THREADS) */
00547 
00548 /*
00549  * Stub functions for non-threaded builds
00550  */
00551 
00552 void
00553 TclInitThreadStorage(void)
00554 {
00555 }
00556 
00557 void
00558 TclpFinalizeThreadDataThread(void)
00559 {
00560 }
00561 
00562 void
00563 TclFinalizeThreadStorage(void)
00564 {
00565 }
00566 
00567 #endif /* defined(TCL_THREADS) && defined(USE_THREAD_STORAGE) */
00568 
00569 /*
00570  * Local Variables:
00571  * mode: c
00572  * c-basic-offset: 4
00573  * fill-column: 78
00574  * End:
00575  */



Generated on Wed Mar 12 12:18:22 2008 by  doxygen 1.5.1