diff options
| author | Eli Zaretskii | 2016-12-10 18:54:43 +0200 |
|---|---|---|
| committer | Eli Zaretskii | 2016-12-10 18:54:43 +0200 |
| commit | 2412a1fc05fe9f89b171d0781c2d530923f48adc (patch) | |
| tree | d42a5d2608e65a10b1cc23c6b4609d54bef25d49 /src/thread.c | |
| parent | fc0fd24c105bde4c001ebebe4b8b7e1f96cd2871 (diff) | |
| parent | 828b4560cd4a0d8cb9b7a7a3e20ff0c53ba86cfa (diff) | |
| download | emacs-2412a1fc05fe9f89b171d0781c2d530923f48adc.tar.gz emacs-2412a1fc05fe9f89b171d0781c2d530923f48adc.zip | |
Support concurrency in Emacs Lisp
Merge branch 'test-concurrency'
* src/thread.c:
* src/thread.h:
* src/systhread.c:
* src/systhread.h: New files.
* src/xgselect.c (xg_select): Avoid using SAFE_NALLOCA and use
xnmalloc unconditionally.
* src/window.c (struct save_window_data): Rename current_buffer to
f_current_buffer.
* src/w32proc.c (sys_select): Change the function signature to
closer fit 'pselect' on Posix hosts.
* src/search.c:
* src/regex.h: Convert some globals to macros that reference
thread-specific values.
* src/process.c (pset_thread, add_non_keyboard_read_fd)
(add_process_read_fd, add_non_blocking_write_fd)
(recompute_input_desc, compute_input_wait_mask)
(compute_non_process_wait_mask, compute_non_keyboard_wait_mask)
(compute_write_mask, clear_waiting_thread_info)
(update_processes_for_thread_death, Fset_process_thread)
(Fprocess_thread): New functions.
(enum fd_bits): New enumeration.
(fd_callback_data): Add 'thread' and 'waiting_thread', rename
'condition' to 'flags'.
(set_process_filter_masks, create_process, create_pty)
(Fmake_serial_process, finish_after_tls_connection)
(connect_network_socket, deactivate_process)
(server_accept_connection, wait_reading_process_output)
(Fcontinue_process, Fstop_process, keyboard_bit_set)
(add_timer_wait_descriptor, add_keyboard_wait_descriptor)
(delete_keyboard_wait_descriptor): Use the new functions instead
of manipulating fd flags and masks directly.
(syms_of_process): Defsubr the new primitives.
* src/print.c (print_object): Print threads, mutexes, and
conditional variables.
* src/lisp.h (enum pvec_type): New values PVEC_THREAD, PVEC_MUTEX,
and PVEC_CONDVAR.
(XTHREAD, XMUTEX, XCONDVAR, THREADP, MUTEXP, CONDVARP)
(CHECK_THREAD, CHECK_MUTEX, CHECK_CONDVAR): New inline functions.
(XSETTHREAD, XSETMUTEX, XSETCONDVAR): New macros.
(struct handler): Add back byte_stack. Rename lisp_eval_depth to
f_lisp_eval_depth.
* src/eval.c (specpdl_kind, specpdl_arg, do_specbind)
(rebind_for_thread_switch, do_one_unbind)
(unbind_for_thread_switch): New functions.
(init_eval): 'handlerlist' is not malloc'ed.
(specbind): Call do_specbind.
(unbind_to): Call do_one_unbind.
(mark_specpdl): Accept 2 arguments.
(mark_specpdl): Mark the saved value in a let-binding.
* src/emacs.c (main): Call init_threads_once, init_threads, and
syms_of_threads.
* src/data.c (Ftype_of): Support thread, mutex, and condvar
objects.
(Fthreadp, Fmutexp, Fcondition_variable_p): New functions.
(syms_of_data): DEFSYM and defsubr new symbols and primitives.
* src/bytecode.c (struct byte_stack, FETCH, CHECK_RANGE)
(BYTE_CODE_QUIT): Add back.
(exec_byte_code): Add back byte stack manipulation.
* src/alloc.c (cleanup_vector): Handle threads, mutexes, and
conditional variables.
(mark_stack): Now extern; accept additional argument 'bottom'.
(flush_stack_call_func): New function.
(garbage_collect_1): Call mark_threads and unmark_threads. Don't
mark handlers.
* src/.gdbinit (xbytecode): Add back.
* test/src/thread-tests.el: New tests.
* test/src/data-tests.el (binding-test-manual)
(binding-test-setq-default, binding-test-makunbound)
(binding-test-defvar-bool, binding-test-defvar-int)
(binding-test-set-constant-t, binding-test-set-constant-nil)
(binding-test-set-constant-keyword)
(binding-test-set-constant-nil): New tests.
* doc/lispref/processes.texi (Processes and Threads): New
subsection.
* doc/lispref/threads.texi: New file
* doc/lispref/elisp.texi (Top): Include it.
* doc/lispref/objects.texi (Thread Type, Mutex Type)
(Condition Variable Type): New subsections.
(Type Predicates): Add thread-related predicates.
* doc/lispref/objects.texi (Editing Types):
* doc/lispref/elisp.texi (Top): Update higher-level menus.
* etc/NEWS: Mention concurrency features.
Diffstat (limited to 'src/thread.c')
| -rw-r--r-- | src/thread.c | 970 |
1 files changed, 970 insertions, 0 deletions
diff --git a/src/thread.c b/src/thread.c new file mode 100644 index 00000000000..ae2ce3dc02b --- /dev/null +++ b/src/thread.c | |||
| @@ -0,0 +1,970 @@ | |||
| 1 | /* Threading code. | ||
| 2 | Copyright (C) 2012, 2013 Free Software Foundation, Inc. | ||
| 3 | |||
| 4 | This file is part of GNU Emacs. | ||
| 5 | |||
| 6 | GNU Emacs is free software: you can redistribute it and/or modify | ||
| 7 | it under the terms of the GNU General Public License as published by | ||
| 8 | the Free Software Foundation, either version 3 of the License, or | ||
| 9 | (at your option) any later version. | ||
| 10 | |||
| 11 | GNU Emacs is distributed in the hope that it will be useful, | ||
| 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 14 | GNU General Public License for more details. | ||
| 15 | |||
| 16 | You should have received a copy of the GNU General Public License | ||
| 17 | along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */ | ||
| 18 | |||
| 19 | |||
| 20 | #include <config.h> | ||
| 21 | #include <setjmp.h> | ||
| 22 | #include "lisp.h" | ||
| 23 | #include "character.h" | ||
| 24 | #include "buffer.h" | ||
| 25 | #include "process.h" | ||
| 26 | #include "coding.h" | ||
| 27 | |||
| 28 | static struct thread_state primary_thread; | ||
| 29 | |||
| 30 | struct thread_state *current_thread = &primary_thread; | ||
| 31 | |||
| 32 | static struct thread_state *all_threads = &primary_thread; | ||
| 33 | |||
| 34 | static sys_mutex_t global_lock; | ||
| 35 | |||
| 36 | extern int poll_suppress_count; | ||
| 37 | extern volatile int interrupt_input_blocked; | ||
| 38 | |||
| 39 | |||
| 40 | |||
| 41 | /* m_specpdl is set when the thread is created and cleared when the | ||
| 42 | thread dies. */ | ||
| 43 | #define thread_alive_p(STATE) ((STATE)->m_specpdl != NULL) | ||
| 44 | |||
| 45 | |||
| 46 | |||
| 47 | static void | ||
| 48 | release_global_lock (void) | ||
| 49 | { | ||
| 50 | sys_mutex_unlock (&global_lock); | ||
| 51 | } | ||
| 52 | |||
| 53 | /* You must call this after acquiring the global lock. | ||
| 54 | acquire_global_lock does it for you. */ | ||
| 55 | static void | ||
| 56 | post_acquire_global_lock (struct thread_state *self) | ||
| 57 | { | ||
| 58 | Lisp_Object buffer; | ||
| 59 | struct thread_state *prev_thread = current_thread; | ||
| 60 | |||
| 61 | /* Do this early on, so that code below could signal errors (e.g., | ||
| 62 | unbind_for_thread_switch might) correctly, because we are already | ||
| 63 | running in the context of the thread pointed by SELF. */ | ||
| 64 | current_thread = self; | ||
| 65 | |||
| 66 | if (prev_thread != current_thread) | ||
| 67 | { | ||
| 68 | /* PREV_THREAD is NULL if the previously current thread | ||
| 69 | exited. In this case, there is no reason to unbind, and | ||
| 70 | trying will crash. */ | ||
| 71 | if (prev_thread != NULL) | ||
| 72 | unbind_for_thread_switch (prev_thread); | ||
| 73 | rebind_for_thread_switch (); | ||
| 74 | } | ||
| 75 | |||
| 76 | /* We need special handling to re-set the buffer. */ | ||
| 77 | XSETBUFFER (buffer, self->m_current_buffer); | ||
| 78 | self->m_current_buffer = 0; | ||
| 79 | set_buffer_internal (XBUFFER (buffer)); | ||
| 80 | |||
| 81 | if (!NILP (current_thread->error_symbol)) | ||
| 82 | { | ||
| 83 | Lisp_Object sym = current_thread->error_symbol; | ||
| 84 | Lisp_Object data = current_thread->error_data; | ||
| 85 | |||
| 86 | current_thread->error_symbol = Qnil; | ||
| 87 | current_thread->error_data = Qnil; | ||
| 88 | Fsignal (sym, data); | ||
| 89 | } | ||
| 90 | } | ||
| 91 | |||
| 92 | static void | ||
| 93 | acquire_global_lock (struct thread_state *self) | ||
| 94 | { | ||
| 95 | sys_mutex_lock (&global_lock); | ||
| 96 | post_acquire_global_lock (self); | ||
| 97 | } | ||
| 98 | |||
| 99 | |||
| 100 | |||
| 101 | static void | ||
| 102 | lisp_mutex_init (lisp_mutex_t *mutex) | ||
| 103 | { | ||
| 104 | mutex->owner = NULL; | ||
| 105 | mutex->count = 0; | ||
| 106 | sys_cond_init (&mutex->condition); | ||
| 107 | } | ||
| 108 | |||
| 109 | static int | ||
| 110 | lisp_mutex_lock (lisp_mutex_t *mutex, int new_count) | ||
| 111 | { | ||
| 112 | struct thread_state *self; | ||
| 113 | |||
| 114 | if (mutex->owner == NULL) | ||
| 115 | { | ||
| 116 | mutex->owner = current_thread; | ||
| 117 | mutex->count = new_count == 0 ? 1 : new_count; | ||
| 118 | return 0; | ||
| 119 | } | ||
| 120 | if (mutex->owner == current_thread) | ||
| 121 | { | ||
| 122 | eassert (new_count == 0); | ||
| 123 | ++mutex->count; | ||
| 124 | return 0; | ||
| 125 | } | ||
| 126 | |||
| 127 | self = current_thread; | ||
| 128 | self->wait_condvar = &mutex->condition; | ||
| 129 | while (mutex->owner != NULL && (new_count != 0 | ||
| 130 | || NILP (self->error_symbol))) | ||
| 131 | sys_cond_wait (&mutex->condition, &global_lock); | ||
| 132 | self->wait_condvar = NULL; | ||
| 133 | |||
| 134 | if (new_count == 0 && !NILP (self->error_symbol)) | ||
| 135 | return 1; | ||
| 136 | |||
| 137 | mutex->owner = self; | ||
| 138 | mutex->count = new_count == 0 ? 1 : new_count; | ||
| 139 | |||
| 140 | return 1; | ||
| 141 | } | ||
| 142 | |||
| 143 | static int | ||
| 144 | lisp_mutex_unlock (lisp_mutex_t *mutex) | ||
| 145 | { | ||
| 146 | if (mutex->owner != current_thread) | ||
| 147 | error ("Cannot unlock mutex owned by another thread"); | ||
| 148 | |||
| 149 | if (--mutex->count > 0) | ||
| 150 | return 0; | ||
| 151 | |||
| 152 | mutex->owner = NULL; | ||
| 153 | sys_cond_broadcast (&mutex->condition); | ||
| 154 | |||
| 155 | return 1; | ||
| 156 | } | ||
| 157 | |||
| 158 | static unsigned int | ||
| 159 | lisp_mutex_unlock_for_wait (lisp_mutex_t *mutex) | ||
| 160 | { | ||
| 161 | unsigned int result = mutex->count; | ||
| 162 | |||
| 163 | /* Ensured by condvar code. */ | ||
| 164 | eassert (mutex->owner == current_thread); | ||
| 165 | |||
| 166 | mutex->count = 0; | ||
| 167 | mutex->owner = NULL; | ||
| 168 | sys_cond_broadcast (&mutex->condition); | ||
| 169 | |||
| 170 | return result; | ||
| 171 | } | ||
| 172 | |||
| 173 | static void | ||
| 174 | lisp_mutex_destroy (lisp_mutex_t *mutex) | ||
| 175 | { | ||
| 176 | sys_cond_destroy (&mutex->condition); | ||
| 177 | } | ||
| 178 | |||
| 179 | static int | ||
| 180 | lisp_mutex_owned_p (lisp_mutex_t *mutex) | ||
| 181 | { | ||
| 182 | return mutex->owner == current_thread; | ||
| 183 | } | ||
| 184 | |||
| 185 | |||
| 186 | |||
| 187 | DEFUN ("make-mutex", Fmake_mutex, Smake_mutex, 0, 1, 0, | ||
| 188 | doc: /* Create a mutex. | ||
| 189 | A mutex provides a synchronization point for threads. | ||
| 190 | Only one thread at a time can hold a mutex. Other threads attempting | ||
| 191 | to acquire it will block until the mutex is available. | ||
| 192 | |||
| 193 | A thread can acquire a mutex any number of times. | ||
| 194 | |||
| 195 | NAME, if given, is used as the name of the mutex. The name is | ||
| 196 | informational only. */) | ||
| 197 | (Lisp_Object name) | ||
| 198 | { | ||
| 199 | struct Lisp_Mutex *mutex; | ||
| 200 | Lisp_Object result; | ||
| 201 | |||
| 202 | if (!NILP (name)) | ||
| 203 | CHECK_STRING (name); | ||
| 204 | |||
| 205 | mutex = ALLOCATE_PSEUDOVECTOR (struct Lisp_Mutex, mutex, PVEC_MUTEX); | ||
| 206 | memset ((char *) mutex + offsetof (struct Lisp_Mutex, mutex), | ||
| 207 | 0, sizeof (struct Lisp_Mutex) - offsetof (struct Lisp_Mutex, | ||
| 208 | mutex)); | ||
| 209 | mutex->name = name; | ||
| 210 | lisp_mutex_init (&mutex->mutex); | ||
| 211 | |||
| 212 | XSETMUTEX (result, mutex); | ||
| 213 | return result; | ||
| 214 | } | ||
| 215 | |||
| 216 | static void | ||
| 217 | mutex_lock_callback (void *arg) | ||
| 218 | { | ||
| 219 | struct Lisp_Mutex *mutex = arg; | ||
| 220 | struct thread_state *self = current_thread; | ||
| 221 | |||
| 222 | if (lisp_mutex_lock (&mutex->mutex, 0)) | ||
| 223 | post_acquire_global_lock (self); | ||
| 224 | } | ||
| 225 | |||
| 226 | static void | ||
| 227 | do_unwind_mutex_lock (void) | ||
| 228 | { | ||
| 229 | current_thread->event_object = Qnil; | ||
| 230 | } | ||
| 231 | |||
| 232 | DEFUN ("mutex-lock", Fmutex_lock, Smutex_lock, 1, 1, 0, | ||
| 233 | doc: /* Acquire a mutex. | ||
| 234 | If the current thread already owns MUTEX, increment the count and | ||
| 235 | return. | ||
| 236 | Otherwise, if no thread owns MUTEX, make the current thread own it. | ||
| 237 | Otherwise, block until MUTEX is available, or until the current thread | ||
| 238 | is signalled using `thread-signal'. | ||
| 239 | Note that calls to `mutex-lock' and `mutex-unlock' must be paired. */) | ||
| 240 | (Lisp_Object mutex) | ||
| 241 | { | ||
| 242 | struct Lisp_Mutex *lmutex; | ||
| 243 | ptrdiff_t count = SPECPDL_INDEX (); | ||
| 244 | |||
| 245 | CHECK_MUTEX (mutex); | ||
| 246 | lmutex = XMUTEX (mutex); | ||
| 247 | |||
| 248 | current_thread->event_object = mutex; | ||
| 249 | record_unwind_protect_void (do_unwind_mutex_lock); | ||
| 250 | flush_stack_call_func (mutex_lock_callback, lmutex); | ||
| 251 | return unbind_to (count, Qnil); | ||
| 252 | } | ||
| 253 | |||
| 254 | static void | ||
| 255 | mutex_unlock_callback (void *arg) | ||
| 256 | { | ||
| 257 | struct Lisp_Mutex *mutex = arg; | ||
| 258 | struct thread_state *self = current_thread; | ||
| 259 | |||
| 260 | if (lisp_mutex_unlock (&mutex->mutex)) | ||
| 261 | post_acquire_global_lock (self); | ||
| 262 | } | ||
| 263 | |||
| 264 | DEFUN ("mutex-unlock", Fmutex_unlock, Smutex_unlock, 1, 1, 0, | ||
| 265 | doc: /* Release the mutex. | ||
| 266 | If this thread does not own MUTEX, signal an error. | ||
| 267 | Otherwise, decrement the mutex's count. If the count is zero, | ||
| 268 | release MUTEX. */) | ||
| 269 | (Lisp_Object mutex) | ||
| 270 | { | ||
| 271 | struct Lisp_Mutex *lmutex; | ||
| 272 | |||
| 273 | CHECK_MUTEX (mutex); | ||
| 274 | lmutex = XMUTEX (mutex); | ||
| 275 | |||
| 276 | flush_stack_call_func (mutex_unlock_callback, lmutex); | ||
| 277 | return Qnil; | ||
| 278 | } | ||
| 279 | |||
| 280 | DEFUN ("mutex-name", Fmutex_name, Smutex_name, 1, 1, 0, | ||
| 281 | doc: /* Return the name of MUTEX. | ||
| 282 | If no name was given when MUTEX was created, return nil. */) | ||
| 283 | (Lisp_Object mutex) | ||
| 284 | { | ||
| 285 | struct Lisp_Mutex *lmutex; | ||
| 286 | |||
| 287 | CHECK_MUTEX (mutex); | ||
| 288 | lmutex = XMUTEX (mutex); | ||
| 289 | |||
| 290 | return lmutex->name; | ||
| 291 | } | ||
| 292 | |||
| 293 | void | ||
| 294 | finalize_one_mutex (struct Lisp_Mutex *mutex) | ||
| 295 | { | ||
| 296 | lisp_mutex_destroy (&mutex->mutex); | ||
| 297 | } | ||
| 298 | |||
| 299 | |||
| 300 | |||
| 301 | DEFUN ("make-condition-variable", | ||
| 302 | Fmake_condition_variable, Smake_condition_variable, | ||
| 303 | 1, 2, 0, | ||
| 304 | doc: /* Make a condition variable associated with MUTEX. | ||
| 305 | A condition variable provides a way for a thread to sleep while | ||
| 306 | waiting for a state change. | ||
| 307 | |||
| 308 | MUTEX is the mutex associated with this condition variable. | ||
| 309 | NAME, if given, is the name of this condition variable. The name is | ||
| 310 | informational only. */) | ||
| 311 | (Lisp_Object mutex, Lisp_Object name) | ||
| 312 | { | ||
| 313 | struct Lisp_CondVar *condvar; | ||
| 314 | Lisp_Object result; | ||
| 315 | |||
| 316 | CHECK_MUTEX (mutex); | ||
| 317 | if (!NILP (name)) | ||
| 318 | CHECK_STRING (name); | ||
| 319 | |||
| 320 | condvar = ALLOCATE_PSEUDOVECTOR (struct Lisp_CondVar, cond, PVEC_CONDVAR); | ||
| 321 | memset ((char *) condvar + offsetof (struct Lisp_CondVar, cond), | ||
| 322 | 0, sizeof (struct Lisp_CondVar) - offsetof (struct Lisp_CondVar, | ||
| 323 | cond)); | ||
| 324 | condvar->mutex = mutex; | ||
| 325 | condvar->name = name; | ||
| 326 | sys_cond_init (&condvar->cond); | ||
| 327 | |||
| 328 | XSETCONDVAR (result, condvar); | ||
| 329 | return result; | ||
| 330 | } | ||
| 331 | |||
| 332 | static void | ||
| 333 | condition_wait_callback (void *arg) | ||
| 334 | { | ||
| 335 | struct Lisp_CondVar *cvar = arg; | ||
| 336 | struct Lisp_Mutex *mutex = XMUTEX (cvar->mutex); | ||
| 337 | struct thread_state *self = current_thread; | ||
| 338 | unsigned int saved_count; | ||
| 339 | Lisp_Object cond; | ||
| 340 | |||
| 341 | XSETCONDVAR (cond, cvar); | ||
| 342 | self->event_object = cond; | ||
| 343 | saved_count = lisp_mutex_unlock_for_wait (&mutex->mutex); | ||
| 344 | /* If we were signalled while unlocking, we skip the wait, but we | ||
| 345 | still must reacquire our lock. */ | ||
| 346 | if (NILP (self->error_symbol)) | ||
| 347 | { | ||
| 348 | self->wait_condvar = &cvar->cond; | ||
| 349 | sys_cond_wait (&cvar->cond, &global_lock); | ||
| 350 | self->wait_condvar = NULL; | ||
| 351 | } | ||
| 352 | lisp_mutex_lock (&mutex->mutex, saved_count); | ||
| 353 | self->event_object = Qnil; | ||
| 354 | post_acquire_global_lock (self); | ||
| 355 | } | ||
| 356 | |||
| 357 | DEFUN ("condition-wait", Fcondition_wait, Scondition_wait, 1, 1, 0, | ||
| 358 | doc: /* Wait for the condition variable COND to be notified. | ||
| 359 | COND is the condition variable to wait on. | ||
| 360 | |||
| 361 | The mutex associated with COND must be held when this is called. | ||
| 362 | It is an error if it is not held. | ||
| 363 | |||
| 364 | This releases the mutex and waits for COND to be notified or for | ||
| 365 | this thread to be signalled with `thread-signal'. When | ||
| 366 | `condition-wait' returns, COND's mutex will again be locked by | ||
| 367 | this thread. */) | ||
| 368 | (Lisp_Object cond) | ||
| 369 | { | ||
| 370 | struct Lisp_CondVar *cvar; | ||
| 371 | struct Lisp_Mutex *mutex; | ||
| 372 | |||
| 373 | CHECK_CONDVAR (cond); | ||
| 374 | cvar = XCONDVAR (cond); | ||
| 375 | |||
| 376 | mutex = XMUTEX (cvar->mutex); | ||
| 377 | if (!lisp_mutex_owned_p (&mutex->mutex)) | ||
| 378 | error ("Condition variable's mutex is not held by current thread"); | ||
| 379 | |||
| 380 | flush_stack_call_func (condition_wait_callback, cvar); | ||
| 381 | |||
| 382 | return Qnil; | ||
| 383 | } | ||
| 384 | |||
| 385 | /* Used to communicate argumnets to condition_notify_callback. */ | ||
| 386 | struct notify_args | ||
| 387 | { | ||
| 388 | struct Lisp_CondVar *cvar; | ||
| 389 | int all; | ||
| 390 | }; | ||
| 391 | |||
| 392 | static void | ||
| 393 | condition_notify_callback (void *arg) | ||
| 394 | { | ||
| 395 | struct notify_args *na = arg; | ||
| 396 | struct Lisp_Mutex *mutex = XMUTEX (na->cvar->mutex); | ||
| 397 | struct thread_state *self = current_thread; | ||
| 398 | unsigned int saved_count; | ||
| 399 | Lisp_Object cond; | ||
| 400 | |||
| 401 | XSETCONDVAR (cond, na->cvar); | ||
| 402 | saved_count = lisp_mutex_unlock_for_wait (&mutex->mutex); | ||
| 403 | if (na->all) | ||
| 404 | sys_cond_broadcast (&na->cvar->cond); | ||
| 405 | else | ||
| 406 | sys_cond_signal (&na->cvar->cond); | ||
| 407 | lisp_mutex_lock (&mutex->mutex, saved_count); | ||
| 408 | post_acquire_global_lock (self); | ||
| 409 | } | ||
| 410 | |||
| 411 | DEFUN ("condition-notify", Fcondition_notify, Scondition_notify, 1, 2, 0, | ||
| 412 | doc: /* Notify COND, a condition variable. | ||
| 413 | This wakes a thread waiting on COND. | ||
| 414 | If ALL is non-nil, all waiting threads are awoken. | ||
| 415 | |||
| 416 | The mutex associated with COND must be held when this is called. | ||
| 417 | It is an error if it is not held. | ||
| 418 | |||
| 419 | This releases COND's mutex when notifying COND. When | ||
| 420 | `condition-notify' returns, the mutex will again be locked by this | ||
| 421 | thread. */) | ||
| 422 | (Lisp_Object cond, Lisp_Object all) | ||
| 423 | { | ||
| 424 | struct Lisp_CondVar *cvar; | ||
| 425 | struct Lisp_Mutex *mutex; | ||
| 426 | struct notify_args args; | ||
| 427 | |||
| 428 | CHECK_CONDVAR (cond); | ||
| 429 | cvar = XCONDVAR (cond); | ||
| 430 | |||
| 431 | mutex = XMUTEX (cvar->mutex); | ||
| 432 | if (!lisp_mutex_owned_p (&mutex->mutex)) | ||
| 433 | error ("Condition variable's mutex is not held by current thread"); | ||
| 434 | |||
| 435 | args.cvar = cvar; | ||
| 436 | args.all = !NILP (all); | ||
| 437 | flush_stack_call_func (condition_notify_callback, &args); | ||
| 438 | |||
| 439 | return Qnil; | ||
| 440 | } | ||
| 441 | |||
| 442 | DEFUN ("condition-mutex", Fcondition_mutex, Scondition_mutex, 1, 1, 0, | ||
| 443 | doc: /* Return the mutex associated with condition variable COND. */) | ||
| 444 | (Lisp_Object cond) | ||
| 445 | { | ||
| 446 | struct Lisp_CondVar *cvar; | ||
| 447 | |||
| 448 | CHECK_CONDVAR (cond); | ||
| 449 | cvar = XCONDVAR (cond); | ||
| 450 | |||
| 451 | return cvar->mutex; | ||
| 452 | } | ||
| 453 | |||
| 454 | DEFUN ("condition-name", Fcondition_name, Scondition_name, 1, 1, 0, | ||
| 455 | doc: /* Return the name of condition variable COND. | ||
| 456 | If no name was given when COND was created, return nil. */) | ||
| 457 | (Lisp_Object cond) | ||
| 458 | { | ||
| 459 | struct Lisp_CondVar *cvar; | ||
| 460 | |||
| 461 | CHECK_CONDVAR (cond); | ||
| 462 | cvar = XCONDVAR (cond); | ||
| 463 | |||
| 464 | return cvar->name; | ||
| 465 | } | ||
| 466 | |||
| 467 | void | ||
| 468 | finalize_one_condvar (struct Lisp_CondVar *condvar) | ||
| 469 | { | ||
| 470 | sys_cond_destroy (&condvar->cond); | ||
| 471 | } | ||
| 472 | |||
| 473 | |||
| 474 | |||
| 475 | struct select_args | ||
| 476 | { | ||
| 477 | select_func *func; | ||
| 478 | int max_fds; | ||
| 479 | fd_set *rfds; | ||
| 480 | fd_set *wfds; | ||
| 481 | fd_set *efds; | ||
| 482 | struct timespec *timeout; | ||
| 483 | sigset_t *sigmask; | ||
| 484 | int result; | ||
| 485 | }; | ||
| 486 | |||
| 487 | static void | ||
| 488 | really_call_select (void *arg) | ||
| 489 | { | ||
| 490 | struct select_args *sa = arg; | ||
| 491 | struct thread_state *self = current_thread; | ||
| 492 | |||
| 493 | release_global_lock (); | ||
| 494 | sa->result = (sa->func) (sa->max_fds, sa->rfds, sa->wfds, sa->efds, | ||
| 495 | sa->timeout, sa->sigmask); | ||
| 496 | acquire_global_lock (self); | ||
| 497 | } | ||
| 498 | |||
| 499 | int | ||
| 500 | thread_select (select_func *func, int max_fds, fd_set *rfds, | ||
| 501 | fd_set *wfds, fd_set *efds, struct timespec *timeout, | ||
| 502 | sigset_t *sigmask) | ||
| 503 | { | ||
| 504 | struct select_args sa; | ||
| 505 | |||
| 506 | sa.func = func; | ||
| 507 | sa.max_fds = max_fds; | ||
| 508 | sa.rfds = rfds; | ||
| 509 | sa.wfds = wfds; | ||
| 510 | sa.efds = efds; | ||
| 511 | sa.timeout = timeout; | ||
| 512 | sa.sigmask = sigmask; | ||
| 513 | flush_stack_call_func (really_call_select, &sa); | ||
| 514 | return sa.result; | ||
| 515 | } | ||
| 516 | |||
| 517 | |||
| 518 | |||
| 519 | static void | ||
| 520 | mark_one_thread (struct thread_state *thread) | ||
| 521 | { | ||
| 522 | struct handler *handler; | ||
| 523 | Lisp_Object tem; | ||
| 524 | |||
| 525 | mark_specpdl (thread->m_specpdl, thread->m_specpdl_ptr); | ||
| 526 | |||
| 527 | mark_stack (thread->m_stack_bottom, thread->stack_top); | ||
| 528 | |||
| 529 | for (handler = thread->m_handlerlist; handler; handler = handler->next) | ||
| 530 | { | ||
| 531 | mark_object (handler->tag_or_ch); | ||
| 532 | mark_object (handler->val); | ||
| 533 | } | ||
| 534 | |||
| 535 | if (thread->m_current_buffer) | ||
| 536 | { | ||
| 537 | XSETBUFFER (tem, thread->m_current_buffer); | ||
| 538 | mark_object (tem); | ||
| 539 | } | ||
| 540 | |||
| 541 | mark_object (thread->m_last_thing_searched); | ||
| 542 | |||
| 543 | if (!NILP (thread->m_saved_last_thing_searched)) | ||
| 544 | mark_object (thread->m_saved_last_thing_searched); | ||
| 545 | } | ||
| 546 | |||
| 547 | static void | ||
| 548 | mark_threads_callback (void *ignore) | ||
| 549 | { | ||
| 550 | struct thread_state *iter; | ||
| 551 | |||
| 552 | for (iter = all_threads; iter; iter = iter->next_thread) | ||
| 553 | { | ||
| 554 | Lisp_Object thread_obj; | ||
| 555 | |||
| 556 | XSETTHREAD (thread_obj, iter); | ||
| 557 | mark_object (thread_obj); | ||
| 558 | mark_one_thread (iter); | ||
| 559 | } | ||
| 560 | } | ||
| 561 | |||
| 562 | void | ||
| 563 | mark_threads (void) | ||
| 564 | { | ||
| 565 | flush_stack_call_func (mark_threads_callback, NULL); | ||
| 566 | } | ||
| 567 | |||
| 568 | void | ||
| 569 | unmark_threads (void) | ||
| 570 | { | ||
| 571 | struct thread_state *iter; | ||
| 572 | |||
| 573 | for (iter = all_threads; iter; iter = iter->next_thread) | ||
| 574 | if (iter->m_byte_stack_list) | ||
| 575 | relocate_byte_stack (iter->m_byte_stack_list); | ||
| 576 | } | ||
| 577 | |||
| 578 | |||
| 579 | |||
| 580 | static void | ||
| 581 | yield_callback (void *ignore) | ||
| 582 | { | ||
| 583 | struct thread_state *self = current_thread; | ||
| 584 | |||
| 585 | release_global_lock (); | ||
| 586 | sys_thread_yield (); | ||
| 587 | acquire_global_lock (self); | ||
| 588 | } | ||
| 589 | |||
| 590 | DEFUN ("thread-yield", Fthread_yield, Sthread_yield, 0, 0, 0, | ||
| 591 | doc: /* Yield the CPU to another thread. */) | ||
| 592 | (void) | ||
| 593 | { | ||
| 594 | flush_stack_call_func (yield_callback, NULL); | ||
| 595 | return Qnil; | ||
| 596 | } | ||
| 597 | |||
| 598 | static Lisp_Object | ||
| 599 | invoke_thread_function (void) | ||
| 600 | { | ||
| 601 | int count = SPECPDL_INDEX (); | ||
| 602 | |||
| 603 | Ffuncall (1, ¤t_thread->function); | ||
| 604 | return unbind_to (count, Qnil); | ||
| 605 | } | ||
| 606 | |||
| 607 | static Lisp_Object | ||
| 608 | do_nothing (Lisp_Object whatever) | ||
| 609 | { | ||
| 610 | return whatever; | ||
| 611 | } | ||
| 612 | |||
| 613 | static void * | ||
| 614 | run_thread (void *state) | ||
| 615 | { | ||
| 616 | char stack_pos; | ||
| 617 | struct thread_state *self = state; | ||
| 618 | struct thread_state **iter; | ||
| 619 | |||
| 620 | self->m_stack_bottom = &stack_pos; | ||
| 621 | self->stack_top = &stack_pos; | ||
| 622 | self->thread_id = sys_thread_self (); | ||
| 623 | |||
| 624 | acquire_global_lock (self); | ||
| 625 | |||
| 626 | { /* Put a dummy catcher at top-level so that handlerlist is never NULL. | ||
| 627 | This is important since handlerlist->nextfree holds the freelist | ||
| 628 | which would otherwise leak every time we unwind back to top-level. */ | ||
| 629 | handlerlist_sentinel = xzalloc (sizeof (struct handler)); | ||
| 630 | handlerlist = handlerlist_sentinel->nextfree = handlerlist_sentinel; | ||
| 631 | struct handler *c = push_handler (Qunbound, CATCHER); | ||
| 632 | eassert (c == handlerlist_sentinel); | ||
| 633 | handlerlist_sentinel->nextfree = NULL; | ||
| 634 | handlerlist_sentinel->next = NULL; | ||
| 635 | } | ||
| 636 | |||
| 637 | /* It might be nice to do something with errors here. */ | ||
| 638 | internal_condition_case (invoke_thread_function, Qt, do_nothing); | ||
| 639 | |||
| 640 | update_processes_for_thread_death (Fcurrent_thread ()); | ||
| 641 | |||
| 642 | xfree (self->m_specpdl - 1); | ||
| 643 | self->m_specpdl = NULL; | ||
| 644 | self->m_specpdl_ptr = NULL; | ||
| 645 | self->m_specpdl_size = 0; | ||
| 646 | |||
| 647 | { | ||
| 648 | struct handler *c, *c_next; | ||
| 649 | for (c = handlerlist_sentinel; c; c = c_next) | ||
| 650 | { | ||
| 651 | c_next = c->nextfree; | ||
| 652 | xfree (c); | ||
| 653 | } | ||
| 654 | } | ||
| 655 | |||
| 656 | current_thread = NULL; | ||
| 657 | sys_cond_broadcast (&self->thread_condvar); | ||
| 658 | |||
| 659 | /* Unlink this thread from the list of all threads. Note that we | ||
| 660 | have to do this very late, after broadcasting our death. | ||
| 661 | Otherwise the GC may decide to reap the thread_state object, | ||
| 662 | leading to crashes. */ | ||
| 663 | for (iter = &all_threads; *iter != self; iter = &(*iter)->next_thread) | ||
| 664 | ; | ||
| 665 | *iter = (*iter)->next_thread; | ||
| 666 | |||
| 667 | release_global_lock (); | ||
| 668 | |||
| 669 | return NULL; | ||
| 670 | } | ||
| 671 | |||
| 672 | void | ||
| 673 | finalize_one_thread (struct thread_state *state) | ||
| 674 | { | ||
| 675 | sys_cond_destroy (&state->thread_condvar); | ||
| 676 | } | ||
| 677 | |||
| 678 | DEFUN ("make-thread", Fmake_thread, Smake_thread, 1, 2, 0, | ||
| 679 | doc: /* Start a new thread and run FUNCTION in it. | ||
| 680 | When the function exits, the thread dies. | ||
| 681 | If NAME is given, it must be a string; it names the new thread. */) | ||
| 682 | (Lisp_Object function, Lisp_Object name) | ||
| 683 | { | ||
| 684 | sys_thread_t thr; | ||
| 685 | struct thread_state *new_thread; | ||
| 686 | Lisp_Object result; | ||
| 687 | const char *c_name = NULL; | ||
| 688 | size_t offset = offsetof (struct thread_state, m_byte_stack_list); | ||
| 689 | |||
| 690 | /* Can't start a thread in temacs. */ | ||
| 691 | if (!initialized) | ||
| 692 | emacs_abort (); | ||
| 693 | |||
| 694 | if (!NILP (name)) | ||
| 695 | CHECK_STRING (name); | ||
| 696 | |||
| 697 | new_thread = ALLOCATE_PSEUDOVECTOR (struct thread_state, m_byte_stack_list, | ||
| 698 | PVEC_THREAD); | ||
| 699 | memset ((char *) new_thread + offset, 0, | ||
| 700 | sizeof (struct thread_state) - offset); | ||
| 701 | |||
| 702 | new_thread->function = function; | ||
| 703 | new_thread->name = name; | ||
| 704 | new_thread->m_last_thing_searched = Qnil; /* copy from parent? */ | ||
| 705 | new_thread->m_saved_last_thing_searched = Qnil; | ||
| 706 | new_thread->m_current_buffer = current_thread->m_current_buffer; | ||
| 707 | new_thread->error_symbol = Qnil; | ||
| 708 | new_thread->error_data = Qnil; | ||
| 709 | new_thread->event_object = Qnil; | ||
| 710 | |||
| 711 | new_thread->m_specpdl_size = 50; | ||
| 712 | new_thread->m_specpdl = xmalloc ((1 + new_thread->m_specpdl_size) | ||
| 713 | * sizeof (union specbinding)); | ||
| 714 | /* Skip the dummy entry. */ | ||
| 715 | ++new_thread->m_specpdl; | ||
| 716 | new_thread->m_specpdl_ptr = new_thread->m_specpdl; | ||
| 717 | |||
| 718 | sys_cond_init (&new_thread->thread_condvar); | ||
| 719 | |||
| 720 | /* We'll need locking here eventually. */ | ||
| 721 | new_thread->next_thread = all_threads; | ||
| 722 | all_threads = new_thread; | ||
| 723 | |||
| 724 | if (!NILP (name)) | ||
| 725 | c_name = SSDATA (ENCODE_UTF_8 (name)); | ||
| 726 | |||
| 727 | if (! sys_thread_create (&thr, c_name, run_thread, new_thread)) | ||
| 728 | { | ||
| 729 | /* Restore the previous situation. */ | ||
| 730 | all_threads = all_threads->next_thread; | ||
| 731 | error ("Could not start a new thread"); | ||
| 732 | } | ||
| 733 | |||
| 734 | /* FIXME: race here where new thread might not be filled in? */ | ||
| 735 | XSETTHREAD (result, new_thread); | ||
| 736 | return result; | ||
| 737 | } | ||
| 738 | |||
| 739 | DEFUN ("current-thread", Fcurrent_thread, Scurrent_thread, 0, 0, 0, | ||
| 740 | doc: /* Return the current thread. */) | ||
| 741 | (void) | ||
| 742 | { | ||
| 743 | Lisp_Object result; | ||
| 744 | XSETTHREAD (result, current_thread); | ||
| 745 | return result; | ||
| 746 | } | ||
| 747 | |||
| 748 | DEFUN ("thread-name", Fthread_name, Sthread_name, 1, 1, 0, | ||
| 749 | doc: /* Return the name of the THREAD. | ||
| 750 | The name is the same object that was passed to `make-thread'. */) | ||
| 751 | (Lisp_Object thread) | ||
| 752 | { | ||
| 753 | struct thread_state *tstate; | ||
| 754 | |||
| 755 | CHECK_THREAD (thread); | ||
| 756 | tstate = XTHREAD (thread); | ||
| 757 | |||
| 758 | return tstate->name; | ||
| 759 | } | ||
| 760 | |||
| 761 | static void | ||
| 762 | thread_signal_callback (void *arg) | ||
| 763 | { | ||
| 764 | struct thread_state *tstate = arg; | ||
| 765 | struct thread_state *self = current_thread; | ||
| 766 | |||
| 767 | sys_cond_broadcast (tstate->wait_condvar); | ||
| 768 | post_acquire_global_lock (self); | ||
| 769 | } | ||
| 770 | |||
| 771 | DEFUN ("thread-signal", Fthread_signal, Sthread_signal, 3, 3, 0, | ||
| 772 | doc: /* Signal an error in a thread. | ||
| 773 | This acts like `signal', but arranges for the signal to be raised | ||
| 774 | in THREAD. If THREAD is the current thread, acts just like `signal'. | ||
| 775 | This will interrupt a blocked call to `mutex-lock', `condition-wait', | ||
| 776 | or `thread-join' in the target thread. */) | ||
| 777 | (Lisp_Object thread, Lisp_Object error_symbol, Lisp_Object data) | ||
| 778 | { | ||
| 779 | struct thread_state *tstate; | ||
| 780 | |||
| 781 | CHECK_THREAD (thread); | ||
| 782 | tstate = XTHREAD (thread); | ||
| 783 | |||
| 784 | if (tstate == current_thread) | ||
| 785 | Fsignal (error_symbol, data); | ||
| 786 | |||
| 787 | /* What to do if thread is already signalled? */ | ||
| 788 | /* What if error_symbol is Qnil? */ | ||
| 789 | tstate->error_symbol = error_symbol; | ||
| 790 | tstate->error_data = data; | ||
| 791 | |||
| 792 | if (tstate->wait_condvar) | ||
| 793 | flush_stack_call_func (thread_signal_callback, tstate); | ||
| 794 | |||
| 795 | return Qnil; | ||
| 796 | } | ||
| 797 | |||
| 798 | DEFUN ("thread-alive-p", Fthread_alive_p, Sthread_alive_p, 1, 1, 0, | ||
| 799 | doc: /* Return t if THREAD is alive, or nil if it has exited. */) | ||
| 800 | (Lisp_Object thread) | ||
| 801 | { | ||
| 802 | struct thread_state *tstate; | ||
| 803 | |||
| 804 | CHECK_THREAD (thread); | ||
| 805 | tstate = XTHREAD (thread); | ||
| 806 | |||
| 807 | return thread_alive_p (tstate) ? Qt : Qnil; | ||
| 808 | } | ||
| 809 | |||
| 810 | DEFUN ("thread--blocker", Fthread_blocker, Sthread_blocker, 1, 1, 0, | ||
| 811 | doc: /* Return the object that THREAD is blocking on. | ||
| 812 | If THREAD is blocked in `thread-join' on a second thread, return that | ||
| 813 | thread. | ||
| 814 | If THREAD is blocked in `mutex-lock', return the mutex. | ||
| 815 | If THREAD is blocked in `condition-wait', return the condition variable. | ||
| 816 | Otherwise, if THREAD is not blocked, return nil. */) | ||
| 817 | (Lisp_Object thread) | ||
| 818 | { | ||
| 819 | struct thread_state *tstate; | ||
| 820 | |||
| 821 | CHECK_THREAD (thread); | ||
| 822 | tstate = XTHREAD (thread); | ||
| 823 | |||
| 824 | return tstate->event_object; | ||
| 825 | } | ||
| 826 | |||
| 827 | static void | ||
| 828 | thread_join_callback (void *arg) | ||
| 829 | { | ||
| 830 | struct thread_state *tstate = arg; | ||
| 831 | struct thread_state *self = current_thread; | ||
| 832 | Lisp_Object thread; | ||
| 833 | |||
| 834 | XSETTHREAD (thread, tstate); | ||
| 835 | self->event_object = thread; | ||
| 836 | self->wait_condvar = &tstate->thread_condvar; | ||
| 837 | while (thread_alive_p (tstate) && NILP (self->error_symbol)) | ||
| 838 | sys_cond_wait (self->wait_condvar, &global_lock); | ||
| 839 | |||
| 840 | self->wait_condvar = NULL; | ||
| 841 | self->event_object = Qnil; | ||
| 842 | post_acquire_global_lock (self); | ||
| 843 | } | ||
| 844 | |||
| 845 | DEFUN ("thread-join", Fthread_join, Sthread_join, 1, 1, 0, | ||
| 846 | doc: /* Wait for THREAD to exit. | ||
| 847 | This blocks the current thread until THREAD exits or until | ||
| 848 | the current thread is signaled. | ||
| 849 | It is an error for a thread to try to join itself. */) | ||
| 850 | (Lisp_Object thread) | ||
| 851 | { | ||
| 852 | struct thread_state *tstate; | ||
| 853 | |||
| 854 | CHECK_THREAD (thread); | ||
| 855 | tstate = XTHREAD (thread); | ||
| 856 | |||
| 857 | if (tstate == current_thread) | ||
| 858 | error ("Cannot join current thread"); | ||
| 859 | |||
| 860 | if (thread_alive_p (tstate)) | ||
| 861 | flush_stack_call_func (thread_join_callback, tstate); | ||
| 862 | |||
| 863 | return Qnil; | ||
| 864 | } | ||
| 865 | |||
| 866 | DEFUN ("all-threads", Fall_threads, Sall_threads, 0, 0, 0, | ||
| 867 | doc: /* Return a list of all the live threads. */) | ||
| 868 | (void) | ||
| 869 | { | ||
| 870 | Lisp_Object result = Qnil; | ||
| 871 | struct thread_state *iter; | ||
| 872 | |||
| 873 | for (iter = all_threads; iter; iter = iter->next_thread) | ||
| 874 | { | ||
| 875 | if (thread_alive_p (iter)) | ||
| 876 | { | ||
| 877 | Lisp_Object thread; | ||
| 878 | |||
| 879 | XSETTHREAD (thread, iter); | ||
| 880 | result = Fcons (thread, result); | ||
| 881 | } | ||
| 882 | } | ||
| 883 | |||
| 884 | return result; | ||
| 885 | } | ||
| 886 | |||
| 887 | |||
| 888 | |||
| 889 | bool | ||
| 890 | thread_check_current_buffer (struct buffer *buffer) | ||
| 891 | { | ||
| 892 | struct thread_state *iter; | ||
| 893 | |||
| 894 | for (iter = all_threads; iter; iter = iter->next_thread) | ||
| 895 | { | ||
| 896 | if (iter == current_thread) | ||
| 897 | continue; | ||
| 898 | |||
| 899 | if (iter->m_current_buffer == buffer) | ||
| 900 | return true; | ||
| 901 | } | ||
| 902 | |||
| 903 | return false; | ||
| 904 | } | ||
| 905 | |||
| 906 | |||
| 907 | |||
| 908 | static void | ||
| 909 | init_primary_thread (void) | ||
| 910 | { | ||
| 911 | primary_thread.header.size | ||
| 912 | = PSEUDOVECSIZE (struct thread_state, m_byte_stack_list); | ||
| 913 | XSETPVECTYPE (&primary_thread, PVEC_THREAD); | ||
| 914 | primary_thread.m_last_thing_searched = Qnil; | ||
| 915 | primary_thread.m_saved_last_thing_searched = Qnil; | ||
| 916 | primary_thread.name = Qnil; | ||
| 917 | primary_thread.function = Qnil; | ||
| 918 | primary_thread.error_symbol = Qnil; | ||
| 919 | primary_thread.error_data = Qnil; | ||
| 920 | primary_thread.event_object = Qnil; | ||
| 921 | } | ||
| 922 | |||
| 923 | void | ||
| 924 | init_threads_once (void) | ||
| 925 | { | ||
| 926 | init_primary_thread (); | ||
| 927 | } | ||
| 928 | |||
| 929 | void | ||
| 930 | init_threads (void) | ||
| 931 | { | ||
| 932 | init_primary_thread (); | ||
| 933 | sys_cond_init (&primary_thread.thread_condvar); | ||
| 934 | sys_mutex_init (&global_lock); | ||
| 935 | sys_mutex_lock (&global_lock); | ||
| 936 | current_thread = &primary_thread; | ||
| 937 | primary_thread.thread_id = sys_thread_self (); | ||
| 938 | } | ||
| 939 | |||
| 940 | void | ||
| 941 | syms_of_threads (void) | ||
| 942 | { | ||
| 943 | #ifndef THREADS_ENABLED | ||
| 944 | if (0) | ||
| 945 | #endif | ||
| 946 | { | ||
| 947 | defsubr (&Sthread_yield); | ||
| 948 | defsubr (&Smake_thread); | ||
| 949 | defsubr (&Scurrent_thread); | ||
| 950 | defsubr (&Sthread_name); | ||
| 951 | defsubr (&Sthread_signal); | ||
| 952 | defsubr (&Sthread_alive_p); | ||
| 953 | defsubr (&Sthread_join); | ||
| 954 | defsubr (&Sthread_blocker); | ||
| 955 | defsubr (&Sall_threads); | ||
| 956 | defsubr (&Smake_mutex); | ||
| 957 | defsubr (&Smutex_lock); | ||
| 958 | defsubr (&Smutex_unlock); | ||
| 959 | defsubr (&Smutex_name); | ||
| 960 | defsubr (&Smake_condition_variable); | ||
| 961 | defsubr (&Scondition_wait); | ||
| 962 | defsubr (&Scondition_notify); | ||
| 963 | defsubr (&Scondition_mutex); | ||
| 964 | defsubr (&Scondition_name); | ||
| 965 | } | ||
| 966 | |||
| 967 | DEFSYM (Qthreadp, "threadp"); | ||
| 968 | DEFSYM (Qmutexp, "mutexp"); | ||
| 969 | DEFSYM (Qcondition_variable_p, "condition-variable-p"); | ||
| 970 | } | ||