aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorTom Tromey2012-08-15 13:09:32 -0600
committerTom Tromey2012-08-15 13:09:32 -0600
commit1dcacbc64721b1a4de58aa36460b0a39e766be63 (patch)
tree98a07fba97225221d3bcfab970070b5a6a6564d6 /src
parent60a9d2a7728895c1a5bfbc37c3bfa8fde35abe61 (diff)
downloademacs-1dcacbc64721b1a4de58aa36460b0a39e766be63.tar.gz
emacs-1dcacbc64721b1a4de58aa36460b0a39e766be63.zip
This adds most of the thread features visible to emacs lisp.
I roughly followed the Bordeaux threads API: http://trac.common-lisp.net/bordeaux-threads/wiki/ApiDocumentation ... but not identically. In particular I chose not to implement interrupt-thread or destroy-thread, but instead a thread-signalling approach. I'm still undecided about *default-special-bindings* (which I did not implement). I think it would be more emacs-like to capture the let bindings at make-thread time, but IIRC Stefan didn't like this idea the first time around. There are one or two semantics issues pointed out in the patch where I could use some advice.
Diffstat (limited to 'src')
-rw-r--r--src/alloc.c3
-rw-r--r--src/data.c15
-rw-r--r--src/emacs.c2
-rw-r--r--src/lisp.h5
-rw-r--r--src/systhread.c15
-rw-r--r--src/thread.c354
-rw-r--r--src/thread.h25
7 files changed, 400 insertions, 19 deletions
diff --git a/src/alloc.c b/src/alloc.c
index dfae2d1ef67..69742a325d1 100644
--- a/src/alloc.c
+++ b/src/alloc.c
@@ -3102,6 +3102,9 @@ sweep_vectors (void)
3102 ptrdiff_t nbytes = PSEUDOVECTOR_NBYTES (vector); 3102 ptrdiff_t nbytes = PSEUDOVECTOR_NBYTES (vector);
3103 ptrdiff_t total_bytes = nbytes; 3103 ptrdiff_t total_bytes = nbytes;
3104 3104
3105 if (PSEUDOVECTOR_TYPEP (&vector->header, PVEC_THREAD))
3106 finalize_one_thread ((struct thread_state *) vector);
3107
3105 next = ADVANCE (vector, nbytes); 3108 next = ADVANCE (vector, nbytes);
3106 3109
3107 /* While NEXT is not marked, try to coalesce with VECTOR, 3110 /* While NEXT is not marked, try to coalesce with VECTOR,
diff --git a/src/data.c b/src/data.c
index d0ef5734abc..fd2194fe1ae 100644
--- a/src/data.c
+++ b/src/data.c
@@ -94,6 +94,7 @@ static Lisp_Object Qchar_table, Qbool_vector, Qhash_table;
94static Lisp_Object Qsubrp, Qmany, Qunevalled; 94static Lisp_Object Qsubrp, Qmany, Qunevalled;
95Lisp_Object Qfont_spec, Qfont_entity, Qfont_object; 95Lisp_Object Qfont_spec, Qfont_entity, Qfont_object;
96static Lisp_Object Qdefun; 96static Lisp_Object Qdefun;
97Lisp_Object Qthread;
97 98
98Lisp_Object Qinteractive_form; 99Lisp_Object Qinteractive_form;
99 100
@@ -211,6 +212,8 @@ for example, (type-of 1) returns `integer'. */)
211 return Qfont_entity; 212 return Qfont_entity;
212 if (FONT_OBJECT_P (object)) 213 if (FONT_OBJECT_P (object))
213 return Qfont_object; 214 return Qfont_object;
215 if (THREADP (object))
216 return Qthread;
214 return Qvector; 217 return Qvector;
215 218
216 case Lisp_Float: 219 case Lisp_Float:
@@ -458,6 +461,16 @@ DEFUN ("floatp", Ffloatp, Sfloatp, 1, 1, 0,
458 return Qnil; 461 return Qnil;
459} 462}
460 463
464DEFUN ("threadp", Fthreadp, Sthreadp, 1, 1, 0,
465 doc: /* Return t if OBJECT is a thread. */)
466 (Lisp_Object object)
467{
468 if (THREADP (object))
469 return Qt;
470 else
471 return Qnil;
472}
473
461 474
462/* Extract and set components of lists */ 475/* Extract and set components of lists */
463 476
@@ -3091,6 +3104,7 @@ syms_of_data (void)
3091 DEFSYM (Qchar_table, "char-table"); 3104 DEFSYM (Qchar_table, "char-table");
3092 DEFSYM (Qbool_vector, "bool-vector"); 3105 DEFSYM (Qbool_vector, "bool-vector");
3093 DEFSYM (Qhash_table, "hash-table"); 3106 DEFSYM (Qhash_table, "hash-table");
3107 DEFSYM (Qthread, "thread");
3094 /* Used by Fgarbage_collect. */ 3108 /* Used by Fgarbage_collect. */
3095 DEFSYM (Qinterval, "interval"); 3109 DEFSYM (Qinterval, "interval");
3096 DEFSYM (Qmisc, "misc"); 3110 DEFSYM (Qmisc, "misc");
@@ -3133,6 +3147,7 @@ syms_of_data (void)
3133 defsubr (&Ssubrp); 3147 defsubr (&Ssubrp);
3134 defsubr (&Sbyte_code_function_p); 3148 defsubr (&Sbyte_code_function_p);
3135 defsubr (&Schar_or_string_p); 3149 defsubr (&Schar_or_string_p);
3150 defsubr (&Sthreadp);
3136 defsubr (&Scar); 3151 defsubr (&Scar);
3137 defsubr (&Scdr); 3152 defsubr (&Scdr);
3138 defsubr (&Scar_safe); 3153 defsubr (&Scar_safe);
diff --git a/src/emacs.c b/src/emacs.c
index ca9f201e8f5..92552521413 100644
--- a/src/emacs.c
+++ b/src/emacs.c
@@ -1552,6 +1552,8 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem
1552 syms_of_ntterm (); 1552 syms_of_ntterm ();
1553#endif /* WINDOWSNT */ 1553#endif /* WINDOWSNT */
1554 1554
1555 syms_of_threads ();
1556
1555 keys_of_casefiddle (); 1557 keys_of_casefiddle ();
1556 keys_of_cmds (); 1558 keys_of_cmds ();
1557 keys_of_buffer (); 1559 keys_of_buffer ();
diff --git a/src/lisp.h b/src/lisp.h
index 2b3d40d3b29..52a523259db 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -554,6 +554,7 @@ clip_to_bounds (ptrdiff_t lower, EMACS_INT num, ptrdiff_t upper)
554#define XBOOL_VECTOR(a) (eassert (BOOL_VECTOR_P (a)), \ 554#define XBOOL_VECTOR(a) (eassert (BOOL_VECTOR_P (a)), \
555 ((struct Lisp_Bool_Vector *) \ 555 ((struct Lisp_Bool_Vector *) \
556 XUNTAG (a, Lisp_Vectorlike))) 556 XUNTAG (a, Lisp_Vectorlike)))
557#define XTHREAD(a) (eassert (THREADP (a)), (struct thread_state *) XPNTR(a))
557 558
558/* Construct a Lisp_Object from a value or address. */ 559/* Construct a Lisp_Object from a value or address. */
559 560
@@ -1822,6 +1823,9 @@ typedef struct {
1822#define CHECK_OVERLAY(x) \ 1823#define CHECK_OVERLAY(x) \
1823 CHECK_TYPE (OVERLAYP (x), Qoverlayp, x) 1824 CHECK_TYPE (OVERLAYP (x), Qoverlayp, x)
1824 1825
1826#define CHECK_THREAD(x) \
1827 CHECK_TYPE (THREADP (x), Qthreadp, x)
1828
1825/* Since we can't assign directly to the CAR or CDR fields of a cons 1829/* Since we can't assign directly to the CAR or CDR fields of a cons
1826 cell, use these when checking that those fields contain numbers. */ 1830 cell, use these when checking that those fields contain numbers. */
1827#define CHECK_NUMBER_CAR(x) \ 1831#define CHECK_NUMBER_CAR(x) \
@@ -2444,6 +2448,7 @@ extern Lisp_Object Qchar_or_string_p, Qmarkerp, Qinteger_or_marker_p, Qvectorp;
2444extern Lisp_Object Qbuffer_or_string_p; 2448extern Lisp_Object Qbuffer_or_string_p;
2445extern Lisp_Object Qfboundp; 2449extern Lisp_Object Qfboundp;
2446extern Lisp_Object Qchar_table_p, Qvector_or_char_table_p; 2450extern Lisp_Object Qchar_table_p, Qvector_or_char_table_p;
2451extern Lisp_Object Qthreadp;
2447 2452
2448extern Lisp_Object Qcdr; 2453extern Lisp_Object Qcdr;
2449 2454
diff --git a/src/systhread.c b/src/systhread.c
index b7147c4fc95..968620bcd1c 100644
--- a/src/systhread.c
+++ b/src/systhread.c
@@ -105,19 +105,12 @@ lisp_mutex_lock (lisp_mutex_t *mutex)
105 } 105 }
106 106
107 self = current_thread; 107 self = current_thread;
108 while (mutex->owner != NULL /* && EQ (self->error_symbol, Qnil) */) 108 self->wait_condvar = &mutex->condition;
109 while (mutex->owner != NULL && EQ (self->error_symbol, Qnil))
109 pthread_cond_wait (&mutex->condition, &global_lock); 110 pthread_cond_wait (&mutex->condition, &global_lock);
111 self->wait_condvar = NULL;
110 112
111#if 0 113 post_acquire_global_lock (self);
112 if (!EQ (self->error_symbol, Qnil))
113 {
114 Lisp_Object error_symbol = self->error_symbol;
115 Lisp_Object data = self->error_data;
116 self->error_symbol = Qnil;
117 self->error_data = Qnil;
118 Fsignal (error_symbol, error_data);
119 }
120#endif
121 114
122 mutex->owner = self; 115 mutex->owner = self;
123 mutex->count = 1; 116 mutex->count = 1;
diff --git a/src/thread.c b/src/thread.c
index 7d2f81ec9ce..5da2e10f1ae 100644
--- a/src/thread.c
+++ b/src/thread.c
@@ -20,15 +20,70 @@ along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */
20#include <config.h> 20#include <config.h>
21#include <setjmp.h> 21#include <setjmp.h>
22#include "lisp.h" 22#include "lisp.h"
23#include "character.h"
24#include "buffer.h"
23 25
24struct thread_state the_only_thread; 26/* FIXME */
27extern void unbind_for_thread_switch (void);
28extern void rebind_for_thread_switch (void);
25 29
26struct thread_state *current_thread = &the_only_thread; 30static struct thread_state primary_thread;
27 31
28struct thread_state *all_threads = &the_only_thread; 32struct thread_state *current_thread = &primary_thread;
33
34static struct thread_state *all_threads = &primary_thread;
29 35
30sys_mutex_t global_lock; 36sys_mutex_t global_lock;
31 37
38Lisp_Object Qthreadp;
39
40
41
42static void
43release_global_lock (void)
44{
45 sys_mutex_unlock (&global_lock);
46}
47
48/* You must call this after acquiring the global lock.
49 acquire_global_lock does it for you. */
50void
51post_acquire_global_lock (struct thread_state *self)
52{
53 Lisp_Object buffer;
54
55 if (self != current_thread)
56 {
57 unbind_for_thread_switch ();
58 current_thread = self;
59 rebind_for_thread_switch ();
60 }
61
62 /* We need special handling to re-set the buffer. */
63 XSETBUFFER (buffer, self->m_current_buffer);
64 self->m_current_buffer = 0;
65 set_buffer_internal (XBUFFER (buffer));
66
67 if (!EQ (current_thread->error_symbol, Qnil))
68 {
69 Lisp_Object sym = current_thread->error_symbol;
70 Lisp_Object data = current_thread->error_data;
71
72 current_thread->error_symbol = Qnil;
73 current_thread->error_data = Qnil;
74 Fsignal (sym, data);
75 }
76}
77
78static void
79acquire_global_lock (struct thread_state *self)
80{
81 sys_mutex_lock (&global_lock);
82 post_acquire_global_lock (self);
83}
84
85
86
32static void 87static void
33mark_one_thread (struct thread_state *thread) 88mark_one_thread (struct thread_state *thread)
34{ 89{
@@ -113,19 +168,302 @@ unmark_threads (void)
113 unmark_byte_stack (iter->m_byte_stack_list); 168 unmark_byte_stack (iter->m_byte_stack_list);
114} 169}
115 170
171
172
173static void
174yield_callback (void *ignore)
175{
176 struct thread_state *self = current_thread;
177
178 release_global_lock ();
179 sys_thread_yield ();
180 acquire_global_lock (self);
181}
182
183DEFUN ("thread-yield", Fthread_yield, Sthread_yield, 0, 0, 0,
184 doc: /* Yield the CPU to another thread. */)
185 (void)
186{
187 flush_stack_call_func (yield_callback, NULL);
188 return Qnil;
189}
190
191static Lisp_Object
192invoke_thread_function (void)
193{
194 Lisp_Object iter;
195
196 int count = SPECPDL_INDEX ();
197
198 Ffuncall (1, &current_thread->function);
199 return unbind_to (count, Qnil);
200}
201
202static Lisp_Object
203do_nothing (Lisp_Object whatever)
204{
205 return whatever;
206}
207
208static void *
209run_thread (void *state)
210{
211 char stack_pos;
212 struct thread_state *self = state;
213 struct thread_state **iter;
214
215 self->m_stack_bottom = &stack_pos;
216 self->stack_top = self->m_stack_bottom = &stack_pos;
217 self->thread_id = sys_thread_self ();
218
219 acquire_global_lock (self);
220
221 /* It might be nice to do something with errors here. */
222 internal_condition_case (invoke_thread_function, Qt, do_nothing);
223
224 unbind_for_thread_switch ();
225
226 /* Unlink this thread from the list of all threads. */
227 for (iter = &all_threads; *iter != self; iter = &(*iter)->next_thread)
228 ;
229 *iter = (*iter)->next_thread;
230
231 self->m_last_thing_searched = Qnil;
232 self->m_saved_last_thing_searched = Qnil;
233 self->name = Qnil;
234 self->function = Qnil;
235 self->error_symbol = Qnil;
236 self->error_data = Qnil;
237 xfree (self->m_specpdl);
238 self->m_specpdl = NULL;
239 self->m_specpdl_ptr = NULL;
240 self->m_specpdl_size = 0;
241
242 sys_cond_broadcast (&self->thread_condvar);
243
244 release_global_lock ();
245
246 return NULL;
247}
248
116void 249void
117init_threads_once (void) 250finalize_one_thread (struct thread_state *state)
118{ 251{
119 the_only_thread.header.size 252 sys_cond_destroy (&state->thread_condvar);
253}
254
255DEFUN ("make-thread", Fmake_thread, Smake_thread, 1, 2, 0,
256 doc: /* Start a new thread and run FUNCTION in it.
257When the function exits, the thread dies.
258If NAME is given, it names the new thread. */)
259 (Lisp_Object function, Lisp_Object name)
260{
261 sys_thread_t thr;
262 struct thread_state *new_thread;
263 Lisp_Object result;
264
265 /* Can't start a thread in temacs. */
266 if (!initialized)
267 abort ();
268
269 new_thread = ALLOCATE_PSEUDOVECTOR (struct thread_state, m_gcprolist,
270 PVEC_THREAD);
271 memset ((char *) new_thread + offsetof (struct thread_state, m_gcprolist),
272 0, sizeof (struct thread_state) - offsetof (struct thread_state,
273 m_gcprolist));
274
275 new_thread->function = function;
276 new_thread->name = name;
277 new_thread->m_last_thing_searched = Qnil; /* copy from parent? */
278 new_thread->m_saved_last_thing_searched = Qnil;
279 new_thread->m_current_buffer = current_thread->m_current_buffer;
280 new_thread->error_symbol = Qnil;
281 new_thread->error_data = Qnil;
282
283 new_thread->m_specpdl_size = 50;
284 new_thread->m_specpdl = xmalloc (new_thread->m_specpdl_size
285 * sizeof (struct specbinding));
286 new_thread->m_specpdl_ptr = new_thread->m_specpdl;
287
288 sys_cond_init (&new_thread->thread_condvar);
289
290 /* We'll need locking here eventually. */
291 new_thread->next_thread = all_threads;
292 all_threads = new_thread;
293
294 if (! sys_thread_create (&thr, run_thread, new_thread))
295 {
296 /* Restore the previous situation. */
297 all_threads = all_threads->next_thread;
298 error ("Could not start a new thread");
299 }
300
301 /* FIXME: race here where new thread might not be filled in? */
302 XSETTHREAD (result, new_thread);
303 return result;
304}
305
306DEFUN ("current-thread", Fcurrent_thread, Scurrent_thread, 0, 0, 0,
307 doc: /* Return the current thread. */)
308 (void)
309{
310 Lisp_Object result;
311 XSETTHREAD (result, current_thread);
312 return result;
313}
314
315DEFUN ("thread-name", Fthread_name, Sthread_name, 1, 1, 0,
316 doc: /* Return the name of the THREAD.
317The name is the same object that was passed to `make-thread'. */)
318 (Lisp_Object thread)
319{
320 struct thread_state *tstate;
321
322 CHECK_THREAD (thread);
323 tstate = XTHREAD (thread);
324
325 return tstate->name;
326}
327
328static void
329thread_signal_callback (void *arg)
330{
331 struct thread_state *tstate = arg;
332 struct thread_state *self = current_thread;
333
334 sys_cond_broadcast (tstate->wait_condvar);
335 post_acquire_global_lock (self);
336}
337
338DEFUN ("thread-signal", Fthread_signal, Sthread_signal, 3, 3, 0,
339 doc: /* FIXME */)
340 (Lisp_Object thread, Lisp_Object error_symbol, Lisp_Object data)
341{
342 struct thread_state *tstate;
343
344 CHECK_THREAD (thread);
345 tstate = XTHREAD (thread);
346
347 if (tstate == current_thread)
348 Fsignal (error_symbol, data);
349
350 /* What to do if thread is already signalled? */
351 /* What if error_symbol is Qnil? */
352 tstate->error_symbol = error_symbol;
353 tstate->error_data = data;
354
355 if (tstate->wait_condvar)
356 flush_stack_call_func (thread_signal_callback, tstate);
357
358 return Qnil;
359}
360
361DEFUN ("thread-alive-p", Fthread_alive_p, Sthread_alive_p, 1, 1, 0,
362 doc: /* FIXME */)
363 (Lisp_Object thread)
364{
365 struct thread_state *tstate;
366
367 CHECK_THREAD (thread);
368 tstate = XTHREAD (thread);
369
370 /* m_specpdl is set when the thread is created and cleared when the
371 thread dies. */
372 return tstate->m_specpdl == NULL ? Qnil : Qt;
373}
374
375static void
376thread_join_callback (void *arg)
377{
378 struct thread_state *tstate = arg;
379 struct thread_state *self = current_thread;
380
381 self->wait_condvar = &tstate->thread_condvar;
382 while (tstate->m_specpdl != NULL && EQ (self->error_symbol, Qnil))
383 sys_cond_wait (self->wait_condvar, &global_lock);
384
385 self->wait_condvar = NULL;
386 post_acquire_global_lock (self);
387}
388
389DEFUN ("thread-join", Fthread_join, Sthread_join, 1, 1, 0,
390 doc: /* FIXME */)
391 (Lisp_Object thread)
392{
393 struct thread_state *tstate;
394
395 CHECK_THREAD (thread);
396 tstate = XTHREAD (thread);
397
398 if (tstate->m_specpdl != NULL)
399 flush_stack_call_func (thread_join_callback, tstate);
400
401 return Qnil;
402}
403
404DEFUN ("all-threads", Fall_threads, Sall_threads, 0, 0, 0,
405 doc: /* Return a list of all threads. */)
406 (void)
407{
408 Lisp_Object result = Qnil;
409 struct thread_state *iter;
410
411 for (iter = all_threads; iter; iter = iter->next_thread)
412 {
413 Lisp_Object thread;
414
415 XSETTHREAD (thread, iter);
416 result = Fcons (thread, result);
417 }
418
419 return result;
420}
421
422
423
424static void
425init_primary_thread (void)
426{
427 primary_thread.header.size
120 = PSEUDOVECSIZE (struct thread_state, m_gcprolist); 428 = PSEUDOVECSIZE (struct thread_state, m_gcprolist);
121 XSETPVECTYPE (&the_only_thread, PVEC_THREAD); 429 XSETPVECTYPE (&primary_thread, PVEC_THREAD);
122 the_only_thread.m_last_thing_searched = Qnil; 430 primary_thread.m_last_thing_searched = Qnil;
123 the_only_thread.m_saved_last_thing_searched = Qnil; 431 primary_thread.m_saved_last_thing_searched = Qnil;
432 primary_thread.name = Qnil;
433 primary_thread.function = Qnil;
434 primary_thread.error_symbol = Qnil;
435 primary_thread.error_data = Qnil;
436
437 sys_cond_init (&primary_thread.thread_condvar);
438}
439
440void
441init_threads_once (void)
442{
443 init_primary_thread ();
124} 444}
125 445
126void 446void
127init_threads (void) 447init_threads (void)
128{ 448{
449 init_primary_thread ();
450
129 sys_mutex_init (&global_lock); 451 sys_mutex_init (&global_lock);
130 sys_mutex_lock (&global_lock); 452 sys_mutex_lock (&global_lock);
131} 453}
454
455void
456syms_of_threads (void)
457{
458 defsubr (&Sthread_yield);
459 defsubr (&Smake_thread);
460 defsubr (&Scurrent_thread);
461 defsubr (&Sthread_name);
462 defsubr (&Sthread_signal);
463 defsubr (&Sthread_alive_p);
464 defsubr (&Sthread_join);
465 defsubr (&Sall_threads);
466
467 Qthreadp = intern_c_string ("threadp");
468 staticpro (&Qthreadp);
469}
diff --git a/src/thread.h b/src/thread.h
index df26b887d1f..3b533316817 100644
--- a/src/thread.h
+++ b/src/thread.h
@@ -34,6 +34,16 @@ struct thread_state
34 Lisp_Object m_saved_last_thing_searched; 34 Lisp_Object m_saved_last_thing_searched;
35#define saved_last_thing_searched (current_thread->m_saved_last_thing_searched) 35#define saved_last_thing_searched (current_thread->m_saved_last_thing_searched)
36 36
37 /* The thread's name. */
38 Lisp_Object name;
39
40 /* The thread's function. */
41 Lisp_Object function;
42
43 /* If non-nil, this thread has been signalled. */
44 Lisp_Object error_symbol;
45 Lisp_Object error_data;
46
37 /* m_gcprolist must be the first non-lisp field. */ 47 /* m_gcprolist must be the first non-lisp field. */
38 /* Recording what needs to be marked for gc. */ 48 /* Recording what needs to be marked for gc. */
39 struct gcpro *m_gcprolist; 49 struct gcpro *m_gcprolist;
@@ -142,6 +152,18 @@ struct thread_state
142 /*re_char*/ unsigned char *m_whitespace_regexp; 152 /*re_char*/ unsigned char *m_whitespace_regexp;
143#define whitespace_regexp (current_thread->m_whitespace_regexp) 153#define whitespace_regexp (current_thread->m_whitespace_regexp)
144 154
155 /* The OS identifier for this thread. */
156 sys_thread_t thread_id;
157
158 /* The condition variable for this thread. This is associated with
159 the global lock. This thread broadcasts to it when it exits. */
160 sys_cond_t thread_condvar;
161
162 /* This thread might be waiting for some condition. If so, this
163 points to the condition. If the thread is interrupted, the
164 interrupter should broadcast to this condition. */
165 sys_cond_t *wait_condvar;
166
145 /* Threads are kept on a linked list. */ 167 /* Threads are kept on a linked list. */
146 struct thread_state *next_thread; 168 struct thread_state *next_thread;
147}; 169};
@@ -149,10 +171,13 @@ struct thread_state
149extern struct thread_state *current_thread; 171extern struct thread_state *current_thread;
150 172
151extern sys_mutex_t global_lock; 173extern sys_mutex_t global_lock;
174extern void post_acquire_global_lock (struct thread_state *);
152 175
153extern void unmark_threads (void); 176extern void unmark_threads (void);
177extern void finalize_one_thread (struct thread_state *state);
154 178
155extern void init_threads_once (void); 179extern void init_threads_once (void);
156extern void init_threads (void); 180extern void init_threads (void);
181extern void syms_of_threads (void);
157 182
158#endif /* THREAD_H */ 183#endif /* THREAD_H */