Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: downloads/tcl8.5.2/generic/tclThreadStorage.c @ 63

Last change on this file since 63 was 25, checked in by landauf, 17 years ago

added tcl to libs

File size: 14.5 KB
Line 
1/*
2 * tclThreadStorage.c --
3 *
4 *      This file implements platform independent thread storage operations.
5 *
6 * Copyright (c) 2003-2004 by Joe Mistachkin
7 *
8 * See the file "license.terms" for information on usage and redistribution of
9 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
10 *
11 * RCS: @(#) $Id: tclThreadStorage.c,v 1.15 2007/12/13 15:23:20 dgp Exp $
12 */
13
14#include "tclInt.h"
15
16#if defined(TCL_THREADS)
17
18/*
19 * This is the thread storage cache array and it's accompanying mutex. The
20 * elements are pairs of thread Id and an associated hash table pointer; the
21 * hash table being pointed to contains the thread storage for it's associated
22 * thread. The purpose of this cache is to minimize the number of hash table
23 * lookups in the master thread storage hash table.
24 */
25
26static Tcl_Mutex threadStorageLock;
27
28/*
29 * This is the struct used for a thread storage cache slot. It contains the
30 * owning thread Id and the associated hash table pointer.
31 */
32
33typedef struct ThreadStorage {
34    Tcl_ThreadId id;            /* the owning thread id */
35    Tcl_HashTable *hashTablePtr;/* the hash table for the thread */
36} ThreadStorage;
37
38/*
39 * These are the prototypes for the custom hash table allocation functions
40 * used by the thread storage subsystem.
41 */
42
43static Tcl_HashEntry *  AllocThreadStorageEntry(Tcl_HashTable *tablePtr,
44                            void *keyPtr);
45static void             FreeThreadStorageEntry(Tcl_HashEntry *hPtr);
46static Tcl_HashTable *  ThreadStorageGetHashTable(Tcl_ThreadId id);
47
48/*
49 * This is the hash key type for thread storage. We MUST use this in
50 * combination with the new hash key type flag TCL_HASH_KEY_SYSTEM_HASH
51 * because these hash tables MAY be used by the threaded memory allocator.
52 */
53
54static Tcl_HashKeyType tclThreadStorageHashKeyType = {
55    TCL_HASH_KEY_TYPE_VERSION,          /* version */
56    TCL_HASH_KEY_SYSTEM_HASH | TCL_HASH_KEY_RANDOMIZE_HASH,
57                                        /* flags */
58    NULL,                               /* hashKeyProc */
59    NULL,                               /* compareKeysProc */
60    AllocThreadStorageEntry,            /* allocEntryProc */
61    FreeThreadStorageEntry              /* freeEntryProc */
62};
63
64/*
65 * This is an invalid thread value.
66 */
67
68#define STORAGE_INVALID_THREAD  (Tcl_ThreadId)0
69
70/*
71 * This is the value for an invalid thread storage key.
72 */
73
74#define STORAGE_INVALID_KEY     0
75
76/*
77 * This is the first valid key for use by external callers. All the values
78 * below this are RESERVED for future use.
79 */
80
81#define STORAGE_FIRST_KEY       1
82
83/*
84 * This is the default number of thread storage cache slots. This define may
85 * need to be fine tuned for maximum performance.
86 */
87
88#define STORAGE_CACHE_SLOTS     97
89
90/*
91 * This is the master thread storage hash table. It is keyed on thread Id and
92 * contains values that are hash tables for each thread. The thread specific
93 * hash tables contain the actual thread storage.
94 */
95
96static Tcl_HashTable threadStorageHashTable;
97
98/*
99 * This is the next thread data key value to use. We increment this everytime
100 * we "allocate" one. It is initially set to 1 in TclInitThreadStorage.
101 */
102
103static int nextThreadStorageKey = STORAGE_INVALID_KEY;
104
105/*
106 * This is the master thread storage cache. Per Kevin Kenny's idea, this
107 * prevents unnecessary lookups for threads that use a lot of thread storage.
108 */
109
110static volatile ThreadStorage threadStorageCache[STORAGE_CACHE_SLOTS];
111
112/*
113 *----------------------------------------------------------------------
114 *
115 * AllocThreadStorageEntry --
116 *
117 *      Allocate space for a Tcl_HashEntry using TclpSysAlloc (not ckalloc).
118 *      We do this because the threaded memory allocator MAY use the thread
119 *      storage hash tables.
120 *
121 * Results:
122 *      The return value is a pointer to the created entry.
123 *
124 * Side effects:
125 *      None.
126 *
127 *----------------------------------------------------------------------
128 */
129
130static Tcl_HashEntry *
131AllocThreadStorageEntry(
132    Tcl_HashTable *tablePtr,    /* Hash table. */
133    void *keyPtr)               /* Key to store in the hash table entry. */
134{
135    Tcl_HashEntry *hPtr;
136
137    hPtr = (Tcl_HashEntry *) TclpSysAlloc(sizeof(Tcl_HashEntry), 0);
138    hPtr->key.oneWordValue = keyPtr;
139    hPtr->clientData = NULL;
140   
141    return hPtr;
142}
143
144/*
145 *----------------------------------------------------------------------
146 *
147 * FreeThreadStorageEntry --
148 *
149 *      Frees space for a Tcl_HashEntry using TclpSysFree (not ckfree). We do
150 *      this because the threaded memory allocator MAY use the thread storage
151 *      hash tables.
152 *
153 * Results:
154 *      None.
155 *
156 * Side effects:
157 *      None.
158 *
159 *----------------------------------------------------------------------
160 */
161
162static void
163FreeThreadStorageEntry(
164    Tcl_HashEntry *hPtr)        /* Hash entry to free. */
165{
166    TclpSysFree((char *) hPtr);
167}
168
169/*
170 *----------------------------------------------------------------------
171 *
172 * ThreadStorageGetHashTable --
173 *
174 *      This procedure returns a hash table pointer to be used for thread
175 *      storage for the specified thread. This assumes that thread storage
176 *      lock is held.
177 *
178 * Results:
179 *      A hash table pointer for the specified thread, or NULL if the hash
180 *      table has not been created yet.
181 *
182 * Side effects:
183 *      May change an entry in the master thread storage cache to point to the
184 *      specified thread and it's associated hash table.
185 *
186 *----------------------------------------------------------------------
187 */
188
189static Tcl_HashTable *
190ThreadStorageGetHashTable(
191    Tcl_ThreadId id)            /* Id of thread to get hash table for */
192{
193    int index = PTR2UINT(id) % STORAGE_CACHE_SLOTS;
194    Tcl_HashEntry *hPtr;
195    int isNew;
196
197    /*
198     * It's important that we pick up the hash table pointer BEFORE comparing
199     * thread Id in case another thread is in the critical region changing
200     * things out from under you.
201     */
202
203    Tcl_HashTable *hashTablePtr = threadStorageCache[index].hashTablePtr;
204
205    if (threadStorageCache[index].id != id) {
206        Tcl_MutexLock(&threadStorageLock);
207
208        /*
209         * It's not in the cache, so we look it up...
210         */
211
212        hPtr = Tcl_FindHashEntry(&threadStorageHashTable, (char *) id);
213
214        if (hPtr != NULL) {
215            /*
216             * We found it, extract the hash table pointer.
217             */
218
219            hashTablePtr = Tcl_GetHashValue(hPtr);
220        } else {
221            /*
222             * The thread specific hash table is not found.
223             */
224
225            hashTablePtr = NULL;
226        }
227
228        if (hashTablePtr == NULL) {
229            hashTablePtr = (Tcl_HashTable *)
230                    TclpSysAlloc(sizeof(Tcl_HashTable), 0);
231
232            if (hashTablePtr == NULL) {
233                Tcl_Panic("could not allocate thread specific hash table, "
234                        "TclpSysAlloc failed from ThreadStorageGetHashTable!");
235            }
236            Tcl_InitCustomHashTable(hashTablePtr, TCL_CUSTOM_TYPE_KEYS,
237                    &tclThreadStorageHashKeyType);
238
239            /*
240             * Add new thread storage hash table to the master hash table.
241             */
242
243            hPtr = Tcl_CreateHashEntry(&threadStorageHashTable, (char *) id,
244                    &isNew);
245
246            if (hPtr == NULL) {
247                Tcl_Panic("Tcl_CreateHashEntry failed from "
248                        "ThreadStorageGetHashTable!");
249            }
250            Tcl_SetHashValue(hPtr, hashTablePtr);
251        }
252
253        /*
254         * Now, we put it in the cache since it is highly likely it will be
255         * needed again shortly.
256         */
257
258        threadStorageCache[index].id = id;
259        threadStorageCache[index].hashTablePtr = hashTablePtr;
260
261        Tcl_MutexUnlock(&threadStorageLock);
262    }
263
264    return hashTablePtr;
265}
266
267/*
268 *----------------------------------------------------------------------
269 *
270 * TclInitThreadStorage --
271 *
272 *      Initializes the thread storage allocator.
273 *
274 * Results:
275 *      None.
276 *
277 * Side effects:
278 *      This procedure initializes the master hash table that maps thread ID
279 *      onto the individual index tables that map thread data key to thread
280 *      data. It also creates a cache that enables fast lookup of the thread
281 *      data block array for a recently executing thread without using
282 *      spinlocks.
283 *
284 * This procedure is called from an extremely early point in Tcl's
285 * initialization. In particular, it may not use ckalloc/ckfree because they
286 * may depend on thread-local storage (it uses TclpSysAlloc and TclpSysFree
287 * instead). It may not depend on synchronization primitives - but no threads
288 * other than the master thread have yet been launched.
289 *
290 *----------------------------------------------------------------------
291 */
292
293void
294TclInitThreadStorage(void)
295{
296    Tcl_InitCustomHashTable(&threadStorageHashTable, TCL_CUSTOM_TYPE_KEYS,
297            &tclThreadStorageHashKeyType);
298
299    /*
300     * We also initialize the cache.
301     */
302
303    memset((void*) &threadStorageCache, 0,
304            sizeof(ThreadStorage) * STORAGE_CACHE_SLOTS);
305
306    /*
307     * Now, we set the first value to be used for a thread data key.
308     */
309
310    nextThreadStorageKey = STORAGE_FIRST_KEY;
311}
312
313/*
314 *----------------------------------------------------------------------
315 *
316 * TclpThreadDataKeyGet --
317 *
318 *      This procedure returns a pointer to a block of thread local storage.
319 *
320 * Results:
321 *      A thread-specific pointer to the data structure, or NULL if the memory
322 *      has not been assigned to this key for this thread.
323 *
324 * Side effects:
325 *      None.
326 *
327 *----------------------------------------------------------------------
328 */
329
330void *
331TclpThreadDataKeyGet(
332    Tcl_ThreadDataKey *keyPtr)  /* Identifier for the data chunk, really
333                                 * (int**) */
334{
335    Tcl_HashTable *hashTablePtr =
336            ThreadStorageGetHashTable(Tcl_GetCurrentThread());
337    Tcl_HashEntry *hPtr = Tcl_FindHashEntry(hashTablePtr, (char *) keyPtr);
338
339    if (hPtr == NULL) {
340        return NULL;
341    }
342    return Tcl_GetHashValue(hPtr);
343}
344
345/*
346 *----------------------------------------------------------------------
347 *
348 * TclpThreadDataKeySet --
349 *
350 *      This procedure sets the pointer to a block of thread local storage.
351 *
352 * Results:
353 *      None.
354 *
355 * Side effects:
356 *      Sets up the thread so future calls to TclpThreadDataKeyGet with this
357 *      key will return the data pointer.
358 *
359 *----------------------------------------------------------------------
360 */
361
362void
363TclpThreadDataKeySet(
364    Tcl_ThreadDataKey *keyPtr,  /* Identifier for the data chunk, really
365                                 * (pthread_key_t **) */
366    void *data)                 /* Thread local storage */
367{
368    Tcl_HashTable *hashTablePtr;
369    Tcl_HashEntry *hPtr;
370    int dummy;
371
372    hashTablePtr = ThreadStorageGetHashTable(Tcl_GetCurrentThread());
373    hPtr = Tcl_CreateHashEntry(hashTablePtr, (char *)keyPtr, &dummy);
374
375    Tcl_SetHashValue(hPtr, data);
376}
377
378/*
379 *----------------------------------------------------------------------
380 *
381 * TclpFinalizeThreadDataThread --
382 *
383 *      This procedure cleans up the thread storage hash table for the
384 *      current thread.
385 *
386 * Results:
387 *      None.
388 *
389 * Side effects:
390 *      Frees all associated thread storage, all hash table entries for
391 *      the thread's thread storage, and the hash table itself.
392 *
393 *----------------------------------------------------------------------
394 */
395
396void
397TclpFinalizeThreadDataThread(void)
398{
399    Tcl_ThreadId id = Tcl_GetCurrentThread();
400                                /* Id of the thread to finalize. */
401    int index = PTR2UINT(id) % STORAGE_CACHE_SLOTS;
402    Tcl_HashEntry *hPtr;        /* Hash entry for current thread in master
403                                 * table. */
404    Tcl_HashTable* hashTablePtr;/* Pointer to the hash table holding TSD
405                                 * blocks for the current thread*/
406    Tcl_HashSearch search;      /* Search object to walk the TSD blocks in the
407                                 * designated thread */
408    Tcl_HashEntry *hPtr2;       /* Hash entry for a TSD block in the
409                                 * designated thread. */
410
411    Tcl_MutexLock(&threadStorageLock);
412    hPtr = Tcl_FindHashEntry(&threadStorageHashTable, (char*)id);
413    if (hPtr == NULL) {
414        hashTablePtr = NULL;
415    } else {
416        /*
417         * We found it, extract the hash table pointer.
418         */
419
420        hashTablePtr = Tcl_GetHashValue(hPtr);
421        Tcl_DeleteHashEntry(hPtr);
422
423        /*
424         * Make sure cache entry for this thread is NULL.
425         */
426
427        if (threadStorageCache[index].id == id) {
428            /*
429             * We do not step on another thread's cache entry. This is
430             * especially important if we are creating and exiting a lot of
431             * threads.
432             */
433
434            threadStorageCache[index].id = STORAGE_INVALID_THREAD;
435            threadStorageCache[index].hashTablePtr = NULL;
436        }
437    }
438    Tcl_MutexUnlock(&threadStorageLock);
439
440    /*
441     * The thread's hash table has been extracted and removed from the master
442     * hash table. Now clean up the thread.
443     */
444
445    if (hashTablePtr != NULL) {
446        /*
447         * Free all TSD
448         */
449
450        for (hPtr2 = Tcl_FirstHashEntry(hashTablePtr, &search); hPtr2 != NULL;
451                hPtr2 = Tcl_NextHashEntry(&search)) {
452            void *blockPtr = Tcl_GetHashValue(hPtr2);
453
454            if (blockPtr != NULL) {
455                /*
456                 * The block itself was allocated in Tcl_GetThreadData using
457                 * ckalloc; use ckfree to dispose of it.
458                 */
459
460                ckfree(blockPtr);
461            }
462        }
463
464        /*
465         * Delete thread specific hash table and free the struct.
466         */
467
468        Tcl_DeleteHashTable(hashTablePtr);
469        TclpSysFree((char *) hashTablePtr);
470    }
471}
472
473/*
474 *----------------------------------------------------------------------
475 *
476 * TclFinalizeThreadStorage --
477 *
478 *      This procedure cleans up the master thread storage hash table, all
479 *      thread specific hash tables, and the thread storage cache.
480 *
481 * Results:
482 *      None.
483 *
484 * Side effects:
485 *      The master thread storage hash table and thread storage cache are
486 *      reset to their initial (empty) state.
487 *
488 *----------------------------------------------------------------------
489 */
490
491void
492TclFinalizeThreadStorage(void)
493{
494    Tcl_HashSearch search;      /* We need to hit every thread with this
495                                 * search. */
496    Tcl_HashEntry *hPtr;        /* Hash entry for current thread in master
497                                 * table. */
498    Tcl_MutexLock(&threadStorageLock);
499
500    /*
501     * We are going to delete the hash table for every thread now. This hash
502     * table should be empty at this point, except for one entry for the
503     * current thread.
504     */
505
506    for (hPtr = Tcl_FirstHashEntry(&threadStorageHashTable, &search);
507            hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
508        Tcl_HashTable *hashTablePtr = Tcl_GetHashValue(hPtr);
509
510        if (hashTablePtr != NULL) {
511            /*
512             * Delete thread specific hash table for the thread in question
513             * and free the struct.
514             */
515
516            Tcl_DeleteHashTable(hashTablePtr);
517            TclpSysFree((char *)hashTablePtr);
518        }
519
520        /*
521         * Delete thread specific entry from master hash table.
522         */
523
524        Tcl_SetHashValue(hPtr, NULL);
525    }
526
527    Tcl_DeleteHashTable(&threadStorageHashTable);
528
529    /*
530     * Clear out the thread storage cache as well.
531     */
532
533    memset((void*) &threadStorageCache, 0,
534            sizeof(ThreadStorage) * STORAGE_CACHE_SLOTS);
535
536    /*
537     * Reset this to zero, it will be set to STORAGE_FIRST_KEY if the thread
538     * storage subsystem gets reinitialized
539     */
540
541    nextThreadStorageKey = STORAGE_INVALID_KEY;
542
543    Tcl_MutexUnlock(&threadStorageLock);
544}
545
546#else /* !defined(TCL_THREADS) */
547
548/*
549 * Stub functions for non-threaded builds
550 */
551
552void
553TclInitThreadStorage(void)
554{
555}
556
557void
558TclpFinalizeThreadDataThread(void)
559{
560}
561
562void
563TclFinalizeThreadStorage(void)
564{
565}
566
567#endif /* defined(TCL_THREADS) && defined(USE_THREAD_STORAGE) */
568
569/*
570 * Local Variables:
571 * mode: c
572 * c-basic-offset: 4
573 * fill-column: 78
574 * End:
575 */
Note: See TracBrowser for help on using the repository browser.