diff options
Diffstat (limited to 'libthreads/cthreads.h')
-rw-r--r-- | libthreads/cthreads.h | 710 |
1 files changed, 710 insertions, 0 deletions
diff --git a/libthreads/cthreads.h b/libthreads/cthreads.h new file mode 100644 index 00000000..d937dcca --- /dev/null +++ b/libthreads/cthreads.h @@ -0,0 +1,710 @@ +/* + * Mach Operating System + * Copyright (c) 1993,1992,1991,1990,1989 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie Mellon + * the rights to redistribute these changes. + */ +/* + * HISTORY + * 20-Oct-93 Tero Kivinen (kivinen) at Helsinki University of Technology + * Renamed cthread_t->catch to to cthread_t->catch_exit, because + * catch is reserved word in c++. + * + * 12-Oct-93 Johannes Helander (jvh) at Helsinki University of Technology + * Added CONDITION_NAMED_INITIALIZER and MUTEX_NAMED_INITIALIZER1 + * macros. They take one argument: a name string. + * + * $Log: cthreads.h,v $ + * Revision 1.19 2002/05/28 23:55:58 roland + * 2002-05-28 Roland McGrath <roland@frob.com> + * + * * cthreads.h (hurd_condition_wait, condition_implies, + * condition_unimplies): Restore decls lost in merge. + * (mutex_clear): Define as mutex_init instead of bogon (lost in merge). + * + * Revision 1.18 2002/05/27 02:50:10 roland + * 2002-05-26 Roland McGrath <roland@frob.com> + * + * Changes merged from CMU MK83a version: + * * cthreads.h, options.h: Various cleanups. + * * call.c, cthread_data.c, sync.c, mig_support.c: Likewise. + * * i386/cthreads.h, i386/thread.c, i386/lock.s: Likewise. + * * cthread_internals.h: Add decls for internal functions. + * (struct cproc): Use vm_offset_t for stack_base and stack_size members. + * Use natural_t for context member. + * * cprocs.c: Use prototypes for all defns. + * * cthreads.c: Likewise. + * (cthread_exit): Cast any_t to integer_t before int. + * + * Revision 2.17 93/05/10 19:43:11 rvb + * Removed include of stdlib.h and just define malloc + * [93/04/27 mrt] + * + * Revision 2.16 93/05/10 17:51:26 rvb + * Just imagine how much more useful TWO special/fast lookup + * variables could be. (Actually, I am planning on using this + * for bsdss -- for multiple threads per task. If I don't, I'll + * remove the feature.) + * [93/05/10 rvb] + * Big mistake here! CTHREAD_DATA must always be set TRUE. + * cthreads.h is included by import_mach.h by lots of files + * that are not compiled with -DCTHREAD_DATA. This means + * they see a different structure for cthread_t than the + * cthread library -- which is compiled with CTHREAD_DATA. + * Also, make cthread_set_data and cthread_data macros. + * [93/05/06 rvb] + * Flush stdlib + * [93/05/05 rvb] + * + * Revision 2.15 93/01/27 09:03:32 danner + * Updated include of mach/mach.h to mach.h + * + * + * Revision 2.14 93/01/24 13:24:50 danner + * Get MACRO_BEGIN, MACRO_END, NEVER, ... from sys/macro_help.h + * why define it here. + * [92/10/20 rvb] + * + * Revision 2.13 93/01/14 18:05:04 danner + * Added MACRO_BEGIN and MACRO_END to definition of spin_lock. + * Fixed return value of cthread_set_data. + * Added prototypes for other miscellaneous functions. + * [92/12/18 pds] + * Converted file to ANSI C. + * Added declarations of cthread_fork_{prepare,parent,child}. + * Added include of <sys/macro_help.h>. + * [92/12/13 pds] + * + * Replaced calloc declaration with an include of stdlib.h. + * [92/06/15 pds] + * 64bit cleanup. + * [92/12/02 af] + * + * Revision 2.12 92/05/22 18:38:36 jfriedl + * From Mike Kupfer <kupfer@sprite.Berkeley.EDU>: + * Add declaration for cthread_wire(). + * Merge in Jonathan Chew's changes for thread-local data. + * Use MACRO_BEGIN and MACRO_END. + * + * Revision 1.8 91/03/25 14:14:49 jjc + * For compatibility with cthread_data: + * 1) Added private_data field to cthread structure + * for use by POSIX thread specific data routines. + * 2) Conditionalized old data field used by cthread_data + * under CTHREAD_DATA for binary compatibility. + * 3) Changed macros, cthread_set_data and cthread_data, + * into routines which use the POSIX routines for + * source compatibility. + * Also, conditionalized under CTHREAD_DATA. + * [91/03/18 jjc] + * Added support for multiplexing the thread specific global + * variable, cthread_data, using the POSIX threads interface + * for thread private data. + * [91/03/14 jjc] + * + * Revision 2.11 91/08/03 18:20:15 jsb + * Removed the infamous line 122. + * [91/08/01 22:40:24 jsb] + * + * Revision 2.10 91/07/31 18:35:42 dbg + * Fix the standard-C conditional: it's __STDC__. + * + * Allow for macro-redefinition of cthread_sp, spin_try_lock, + * spin_unlock (from machine/cthreads.h). + * [91/07/30 17:34:28 dbg] + * + * Revision 2.9 91/05/14 17:56:42 mrt + * Correcting copyright + * + * Revision 2.8 91/02/14 14:19:52 mrt + * Added new Mach copyright + * [91/02/13 12:41:15 mrt] + * + * Revision 2.7 90/11/05 14:37:12 rpd + * Include machine/cthreads.h. Added spin_lock_t. + * [90/10/31 rwd] + * + * Revision 2.6 90/10/12 13:07:24 rpd + * Channge to allow for positive stack growth. + * [90/10/10 rwd] + * + * Revision 2.5 90/09/09 14:34:56 rpd + * Remove mutex_special and debug_mutex. + * [90/08/24 rwd] + * + * Revision 2.4 90/08/07 14:31:14 rpd + * Removed RCS keyword nonsense. + * + * Revision 2.3 90/01/19 14:37:18 rwd + * Add back pointer to cthread structure. + * [90/01/03 rwd] + * Change definition of cthread_init and change ur_cthread_self macro + * to reflect movement of self pointer on stack. + * [89/12/18 19:18:34 rwd] + * + * Revision 2.2 89/12/08 19:53:49 rwd + * Change spin_try_lock to int. + * [89/11/30 rwd] + * Changed mutex macros to deal with special mutexs + * [89/11/26 rwd] + * Make mutex_{set,clear}_special routines instead of macros. + * [89/11/25 rwd] + * Added mutex_special to specify a need to context switch on this + * mutex. + * [89/11/21 rwd] + * + * Made mutex_lock a macro trying to grab the spin_lock first. + * [89/11/13 rwd] + * Removed conditionals. Mutexes are more like conditions now. + * Changed for limited kernel thread version. + * [89/10/23 rwd] + * + * Revision 2.1 89/08/03 17:09:40 rwd + * Created. + * + * + * 28-Oct-88 Eric Cooper (ecc) at Carnegie Mellon University + * Implemented spin_lock() as test and test-and-set logic + * (using mutex_try_lock()) in sync.c. Changed ((char *) 0) + * to 0, at Mike Jones's suggestion, and turned on ANSI-style + * declarations in either C++ or _STDC_. + * + * 29-Sep-88 Eric Cooper (ecc) at Carnegie Mellon University + * Changed NULL to ((char *) 0) to avoid dependency on <stdio.h>, + * at Alessandro Forin's suggestion. + * + * 08-Sep-88 Alessandro Forin (af) at Carnegie Mellon University + * Changed queue_t to cthread_queue_t and string_t to char * + * to avoid conflicts. + * + * 01-Apr-88 Eric Cooper (ecc) at Carnegie Mellon University + * Changed compound statement macros to use the + * do { ... } while (0) trick, so that they work + * in all statement contexts. + * + * 19-Feb-88 Eric Cooper (ecc) at Carnegie Mellon University + * Made spin_unlock() and mutex_unlock() into procedure calls + * rather than macros, so that even smart compilers can't reorder + * the clearing of the lock. Suggested by Jeff Eppinger. + * Removed the now empty <machine>/cthreads.h. + * + * 01-Dec-87 Eric Cooper (ecc) at Carnegie Mellon University + * Changed cthread_self() to mask the current SP to find + * the self pointer stored at the base of the stack. + * + * 22-Jul-87 Eric Cooper (ecc) at Carnegie Mellon University + * Fixed bugs in mutex_set_name and condition_set_name + * due to bad choice of macro formal parameter name. + * + * 21-Jul-87 Eric Cooper (ecc) at Carnegie Mellon University + * Moved #include <machine/cthreads.h> to avoid referring + * to types before they are declared (required by C++). + * + * 9-Jul-87 Michael Jones (mbj) at Carnegie Mellon University + * Added conditional type declarations for C++. + * Added _cthread_init_routine and _cthread_exit_routine variables + * for automatic initialization and finalization by crt0. + */ +/* + * File: cthreads.h + * Author: Eric Cooper, Carnegie Mellon University + * Date: Jul, 1987 + * + * Definitions for the C Threads package. + * + */ + + +#ifndef _CTHREADS_ +#define _CTHREADS_ 1 + +#if 0 +/* This is CMU's machine-dependent file. In GNU all of the machine + dependencies are dealt with in libc. */ +#include <machine/cthreads.h> +#include <mach.h> +#include <sys/macro_help.h> +#include <mach/machine/vm_param.h> + +#ifdef __STDC__ +extern void *malloc(); +#else +extern char *malloc(); +#endif + +#else /* GNU */ +# include <stdlib.h> +# include <mach.h> +# include <mach/machine/vm_param.h> +# include <machine-sp.h> +# define cthread_sp() ((vm_address_t) __thread_stack_pointer ()) +# define MACRO_BEGIN __extension__ ({ +# define MACRO_END 0; }) +#endif + +typedef void *any_t; /* XXX - obsolete, should be deleted. */ + +#if defined(TRUE) +#else /* not defined(TRUE) */ +#define TRUE 1 +#define FALSE 0 +#endif + +/* Enable mutex holder debugging */ +/* #define WAIT_DEBUG */ +/* Record function name instead of thread pointer */ +/* #define WAIT_FUNC_DEBUG */ + +/* + * C Threads package initialization. + */ + +extern vm_offset_t cthread_init(void); + + +/* + * Queues. + */ +typedef struct cthread_queue { + struct cthread_queue_item *head; + struct cthread_queue_item *tail; +} *cthread_queue_t; + +typedef struct cthread_queue_item { + struct cthread_queue_item *next; +} *cthread_queue_item_t; + +#define NO_QUEUE_ITEM ((cthread_queue_item_t) 0) + +#define QUEUE_INITIALIZER { NO_QUEUE_ITEM, NO_QUEUE_ITEM } + +#define cthread_queue_alloc() ((cthread_queue_t) calloc(1, sizeof(struct cthread_queue))) +#define cthread_queue_init(q) ((q)->head = (q)->tail = 0) +#define cthread_queue_free(q) free((q)) + +#define cthread_queue_enq(q, x) \ + MACRO_BEGIN \ + (x)->next = 0; \ + if ((q)->tail == 0) \ + (q)->head = (cthread_queue_item_t) (x); \ + else \ + (q)->tail->next = (cthread_queue_item_t) (x); \ + (q)->tail = (cthread_queue_item_t) (x); \ + MACRO_END + +#define cthread_queue_preq(q, x) \ + MACRO_BEGIN \ + if ((q)->tail == 0) \ + (q)->tail = (cthread_queue_item_t) (x); \ + ((cthread_queue_item_t) (x))->next = (q)->head; \ + (q)->head = (cthread_queue_item_t) (x); \ + MACRO_END + +#define cthread_queue_head(q, t) ((t) ((q)->head)) + +#define cthread_queue_deq(q, t, x) \ + MACRO_BEGIN \ + if (((x) = (t) ((q)->head)) != 0 && \ + ((q)->head = (cthread_queue_item_t) ((x)->next)) == 0) \ + (q)->tail = 0; \ + MACRO_END + +#define cthread_queue_map(q, t, f) \ + MACRO_BEGIN \ + register cthread_queue_item_t x, next; \ + for (x = (cthread_queue_item_t) ((q)->head); x != 0; x = next){\ + next = x->next; \ + (*(f))((t) x); \ + } \ + MACRO_END + +#if 1 + +/* In GNU, spin locks are implemented in libc. + Just include its header file. */ +#include <spin-lock.h> + +#else /* Unused CMU code. */ + +/* + * Spin locks. + */ +extern void spin_lock_solid(spin_lock_t *_lock); + +#if defined(spin_unlock) +#else /* not defined(spin_unlock) */ +extern void spin_unlock(spin_lock_t *_lock); +#endif + +#if defined(spin_try_lock) +#else /* not defined(spin_try_lock) */ +extern boolean_t spin_try_lock(spin_lock_t *_lock); +#endif + +#define spin_lock(p) \ + MACRO_BEGIN \ + if (!spin_try_lock(p)) { \ + spin_lock_solid(p); \ + } \ + MACRO_END + +#endif /* End unused CMU code. */ + +/* + * Mutex objects. + */ +typedef struct mutex { + /* The `held' member must be first in GNU. The GNU C library relies on + being able to cast a `struct mutex *' to a `spin_lock_t *' (which is + kosher if it is the first member) and spin_try_lock that address to + see if it gets the mutex. */ + spin_lock_t held; + spin_lock_t lock; + const char *name; + struct cthread_queue queue; + /* holder is for WAIT_DEBUG. Not ifdeffed to keep size constant. */ +#ifdef WAIT_FUNC_DEBUG + const char *fname; +#else /* WAIT_FUNC_DEBUG */ + struct cthread *holder; +#endif /* WAIT_FUNC_DEBUG */ +} *mutex_t; + +#ifdef WAIT_DEBUG +#ifdef WAIT_FUNC_DEBUG +#define WAIT_CLEAR_DEBUG(m) (m)->fname = 0 +#define WAIT_SET_DEBUG(m) (m)->fname = __FUNCTION__ +#else /* WAIT_FUNC_DEBUG */ +#define WAIT_CLEAR_DEBUG(m) (m)->holder = 0 +#define WAIT_SET_DEBUG(m) (m)->holder = cthread_self() +#endif /* WAIT_FUNC_DEBUG */ +#else /* WAIT_DEBUG */ +#define WAIT_CLEAR_DEBUG(m) (void) 0 +#define WAIT_SET_DEBUG(m) (void) 0 +#endif /* WAIT_DEBUG */ + +/* Rearranged accordingly for GNU: */ +#define MUTEX_INITIALIZER { SPIN_LOCK_INITIALIZER, SPIN_LOCK_INITIALIZER, 0, QUEUE_INITIALIZER, } +#define MUTEX_NAMED_INITIALIZER(Name) { SPIN_LOCK_INITIALIZER, SPIN_LOCK_INITIALIZER, Name, QUEUE_INITIALIZER, } + +#define mutex_alloc() ((mutex_t) calloc(1, sizeof(struct mutex))) +#define mutex_init(m) \ + MACRO_BEGIN \ + spin_lock_init(&(m)->lock); \ + cthread_queue_init(&(m)->queue); \ + spin_lock_init(&(m)->held); \ + WAIT_CLEAR_DEBUG(m); \ + MACRO_END +#define mutex_set_name(m, x) ((m)->name = (x)) +#define mutex_name(m) ((m)->name != 0 ? (m)->name : "?") +#define mutex_clear(m) mutex_init(m) +#define mutex_free(m) free((m)) + +#define mutex_try_lock(m) (spin_try_lock(&(m)->held) ? WAIT_SET_DEBUG(m), 1 : 0) +#define mutex_lock(m) \ + MACRO_BEGIN \ + if (!spin_try_lock(&(m)->held)) { \ + __mutex_lock_solid(m); \ + } \ + WAIT_SET_DEBUG(m); \ + MACRO_END +#define mutex_unlock(m) \ + MACRO_BEGIN \ + if (spin_unlock(&(m)->held), \ + cthread_queue_head(&(m)->queue, vm_offset_t) != 0) { \ + __mutex_unlock_solid(m); \ + } \ + WAIT_CLEAR_DEBUG(m); \ + MACRO_END +/* + * Condition variables. + */ +typedef struct condition { + spin_lock_t lock; + struct cthread_queue queue; + const char *name; + struct cond_imp *implications; +} *condition_t; + +struct cond_imp +{ + struct condition *implicatand; + struct cond_imp *next; +}; + +#define CONDITION_INITIALIZER { SPIN_LOCK_INITIALIZER, QUEUE_INITIALIZER, 0, 0 } +#define CONDITION_NAMED_INITIALIZER(Name) { SPIN_LOCK_INITIALIZER, QUEUE_INITIALIZER, Name, 0 } + +#define condition_alloc() \ + ((condition_t) calloc(1, sizeof(struct condition))) +#define condition_init(c) \ + MACRO_BEGIN \ + spin_lock_init(&(c)->lock); \ + cthread_queue_init(&(c)->queue); \ + (c)->name = 0; \ + (c)->implications = 0; \ + MACRO_END +#define condition_set_name(c, x) ((c)->name = (x)) +#define condition_name(c) ((c)->name != 0 ? (c)->name : "?") +#define condition_clear(c) \ + MACRO_BEGIN \ + condition_broadcast(c); \ + spin_lock(&(c)->lock); \ + MACRO_END +#define condition_free(c) \ + MACRO_BEGIN \ + condition_clear(c); \ + free((c)); \ + MACRO_END + +#define condition_signal(c) \ + MACRO_BEGIN \ + if ((c)->queue.head || (c)->implications) { \ + cond_signal(c); \ + } \ + MACRO_END + +#define condition_broadcast(c) \ + MACRO_BEGIN \ + if ((c)->queue.head || (c)->implications) { \ + cond_broadcast(c); \ + } \ + MACRO_END + +extern int cond_signal(condition_t _cond); + +extern void cond_broadcast(condition_t _cond); + +extern void condition_wait(condition_t _cond, mutex_t _mutex); +extern int hurd_condition_wait(condition_t _cond, mutex_t _mutex); + +extern void condition_implies(condition_t _implicator, + condition_t _implicatand); +extern void condition_unimplies(condition_t _implicator, + condition_t _implicatand); + +/* + * Threads. + */ + +typedef void * (*cthread_fn_t)(void *arg); + +#include <setjmp.h> + +typedef struct cthread { + struct cthread *next; + struct mutex lock; + struct condition done; + int state; + jmp_buf catch_exit; + cthread_fn_t func; + void *arg; + void *result; + const char *name; + void *data; + void *ldata; + void *private_data; + struct ur_cthread *ur; +} *cthread_t; + +#define NO_CTHREAD ((cthread_t) 0) + +extern cthread_t cthread_fork(cthread_fn_t _func, void *_arg); + +extern void cthread_detach(cthread_t _thread); + +extern any_t cthread_join(cthread_t _thread); + +extern void cthread_yield(void); + +extern void cthread_exit(void *_result); + +/* + * This structure must agree with struct cproc in cthread_internals.h + */ +typedef struct ur_cthread { + struct ur_cthread *next; + cthread_t incarnation; +} *ur_cthread_t; + +#ifndef cthread_sp +extern vm_offset_t +cthread_sp(void); +#endif + +extern vm_offset_t cthread_stack_mask; + +#if defined(STACK_GROWTH_UP) +#define ur_cthread_ptr(sp) \ + (* (ur_cthread_t *) ((sp) & cthread_stack_mask)) +#else /* not defined(STACK_GROWTH_UP) */ +#define ur_cthread_ptr(sp) \ + (* (ur_cthread_t *) ( ((sp) | cthread_stack_mask) + 1 \ + - sizeof(ur_cthread_t *)) ) +#endif /* defined(STACK_GROWTH_UP) */ + +#define ur_cthread_self() (ur_cthread_ptr(cthread_sp())) + +#define cthread_assoc(id, t) ((((ur_cthread_t) (id))->incarnation = (t)), \ + ((t) ? ((t)->ur = (ur_cthread_t)(id)) : 0)) +#define cthread_self() (ur_cthread_self()->incarnation) + +extern void cthread_set_name(cthread_t _thread, const char *_name); + +extern const char * cthread_name(cthread_t _thread); + +extern int cthread_count(void); + +extern void cthread_set_limit(int _limit); + +extern int cthread_limit(void); + +extern void cthread_set_kernel_limit(int _n); + +extern int cthread_kernel_limit(void); + +extern void cthread_wire(void); + +extern void cthread_unwire(void); + +extern void cthread_msg_busy(mach_port_t _port, int _min, int _max); + +extern void cthread_msg_active(mach_port_t _prt, int _min, int _max); + +extern mach_msg_return_t cthread_mach_msg(mach_msg_header_t *_header, + mach_msg_option_t _option, + mach_msg_size_t _send_size, + mach_msg_size_t _rcv_size, + mach_port_t _rcv_name, + mach_msg_timeout_t _timeout, + mach_port_t _notify, + int _min, int _max); + +extern void cthread_fork_prepare(void); + +extern void cthread_fork_parent(void); + +extern void cthread_fork_child(void); + +#if defined(THREAD_CALLS) +/* + * Routines to replace thread_*. + */ +extern kern_return_t cthread_get_state(cthread_t _thread); + +extern kern_return_t cthread_set_state(cthread_t _thread); + +extern kern_return_t cthread_abort(cthread_t _thread); + +extern kern_return_t cthread_resume(cthread_t _thread); + +extern kern_return_t cthread_suspend(cthread_t _thread); + +extern kern_return_t cthread_call_on(cthread_t _thread); +#endif /* defined(THREAD_CALLS) */ + +#if defined(CTHREAD_DATA_XX) +/* + * Set or get thread specific "global" variable + * + * The thread given must be the calling thread (ie. thread_self). + * XXX This is for compatibility with the old cthread_data. XXX + */ +extern int cthread_set_data(cthread_t _thread, void *_val); + +extern void * cthread_data(cthread_t _thread); +#else /* defined(CTHREAD_DATA_XX) */ + +#define cthread_set_data(_thread, _val) ((_thread)->data) = (void *)(_val); +#define cthread_data(_thread) ((_thread)->data) + +#define cthread_set_ldata(_thread, _val) ((_thread)->ldata) = (void *)(_val); +#define cthread_ldata(_thread) ((_thread)->ldata) + +#endif /* defined(CTHREAD_DATA_XX) */ + + +/* + * Support for POSIX thread specific data + * + * Multiplexes a thread specific "global" variable + * into many thread specific "global" variables. + */ +#define CTHREAD_DATA_VALUE_NULL (void *)0 +#define CTHREAD_KEY_INVALID (cthread_key_t)-1 + +typedef int cthread_key_t; + +/* + * Create key to private data visible to all threads in task. + * Different threads may use same key, but the values bound to the key are + * maintained on a thread specific basis. + */ +extern int cthread_keycreate(cthread_key_t *_key); + +/* + * Get value currently bound to key for calling thread + */ +extern int cthread_getspecific(cthread_key_t _key, void **_value); + +/* + * Bind value to given key for calling thread + */ +extern int cthread_setspecific(cthread_key_t _key, void *_value); + +/* + * Debugging support. + */ +#if defined(DEBUG) + +#if defined(ASSERT) +#else /* not defined(ASSERT) */ +/* + * Assertion macro, similar to <assert.h> + */ +#include <stdio.h> +#define ASSERT(p) \ + MACRO_BEGIN \ + if (!(p)) { \ + fprintf(stderr, \ + "File %s, line %d: assertion p failed.\n", \ + __FILE__, __LINE__); \ + abort(); \ + } \ + MACRO_END + +#endif /* defined(ASSERT) */ + +#define SHOULDNT_HAPPEN 0 + +extern int cthread_debug; + +#else /* not defined(DEBUG) */ + +#if defined(ASSERT) +#else /* not defined(ASSERT) */ +#define ASSERT(p) +#endif /* defined(ASSERT) */ + +#endif /* defined(DEBUG) */ + +#endif /* not defined(_CTHREADS_) */ |