mirror of
https://git.savannah.gnu.org/git/guile.git
synced 2025-05-20 11:40:18 +02:00
Add thread local fluids
* libguile/fluids.h (struct scm_dynamic_state): Add thread_local_values table. Thread locals are flushed to a separate thread-local table. The references are strong references since the table never escapes the thread. (scm_make_thread_local_fluid, scm_fluid_thread_local_p): New functions. * libguile/fluids.c (FLUID_F_THREAD_LOCAL): (SCM_I_FLUID_THREAD_LOCAL_P): New macros. (restore_dynamic_state): Add comment about precondition. (save_dynamic_state): Flush thread locals. (scm_i_fluid_print): Print thread locals nicely. (new_fluid): Add flags arg. (scm_make_fluid, scm_make_fluid_with_default, scm_make_unbound_fluid): Adapt. (scm_make_thread_local_fluid, scm_fluid_thread_local_p): New functions. (fluid_set_x): Special flushing logic for thread-locals. (fluid_ref): Special cache miss logic for thread locals. * libguile/stacks.c (scm_init_stacks): * libguile/throw.c (scm_init_throw): %stacks and %exception-handler are thread-locals. * libguile/threads.c (guilify_self_2): Init thread locals table. * test-suite/tests/fluids.test ("dynamic states"): Add test. * doc/ref/api-control.texi (Fluids and Dynamic States): Add link to Thread-Local Variables. * doc/ref/api-scheduling.texi (Thread Local Variables): Update with real thread-locals. * NEWS: Update.
This commit is contained in:
parent
84a740d86a
commit
fb8c91a35c
9 changed files with 125 additions and 32 deletions
7
NEWS
7
NEWS
|
@ -23,6 +23,13 @@ guile2.2, guile-2, guile2, and then guile. The found prefix is also
|
||||||
applied to guild, guile-config, and the like. Thanks to Freja Nordsiek
|
applied to guild, guile-config, and the like. Thanks to Freja Nordsiek
|
||||||
for this work.
|
for this work.
|
||||||
|
|
||||||
|
** Add thread-local fluids
|
||||||
|
|
||||||
|
Guile now has support for fluids whose values are not captured by
|
||||||
|
`current-dynamic-state' and not inheritied by child threads, and thus
|
||||||
|
are local to the kernel thread they run on. See "Thread-Local
|
||||||
|
Variables" in the manual, for more.
|
||||||
|
|
||||||
* Bug fixes
|
* Bug fixes
|
||||||
|
|
||||||
** Fix type inference when multiplying flonum with complex
|
** Fix type inference when multiplying flonum with complex
|
||||||
|
|
|
@ -1727,6 +1727,9 @@ used for testing whether an object is actually a fluid. The values
|
||||||
stored in a fluid can be accessed with @code{fluid-ref} and
|
stored in a fluid can be accessed with @code{fluid-ref} and
|
||||||
@code{fluid-set!}.
|
@code{fluid-set!}.
|
||||||
|
|
||||||
|
@xref{Thread-Local Variables}, for further notes on fluids, threads,
|
||||||
|
parameters, and dynamic states.
|
||||||
|
|
||||||
@deffn {Scheme Procedure} make-fluid [dflt]
|
@deffn {Scheme Procedure} make-fluid [dflt]
|
||||||
@deffnx {C Function} scm_make_fluid ()
|
@deffnx {C Function} scm_make_fluid ()
|
||||||
@deffnx {C Function} scm_make_fluid_with_default (dflt)
|
@deffnx {C Function} scm_make_fluid_with_default (dflt)
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
@menu
|
@menu
|
||||||
* Threads:: Multiple threads of execution.
|
* Threads:: Multiple threads of execution.
|
||||||
* Thread Local Variables:: Guile doesn't really have these.
|
* Thread Local Variables:: Some fluids are thread-local.
|
||||||
* Asyncs:: Asynchronous interrupts.
|
* Asyncs:: Asynchronous interrupts.
|
||||||
* Atomics:: Atomic references.
|
* Atomics:: Atomic references.
|
||||||
* Mutexes and Condition Variables:: Synchronization primitives.
|
* Mutexes and Condition Variables:: Synchronization primitives.
|
||||||
|
@ -169,9 +169,7 @@ information.
|
||||||
@subsection Thread-Local Variables
|
@subsection Thread-Local Variables
|
||||||
|
|
||||||
Sometimes you want to establish a variable binding that is only valid
|
Sometimes you want to establish a variable binding that is only valid
|
||||||
for a given thread: a ``thread-local variable''. Guile doesn't really
|
for a given thread: a ``thread-local variable''.
|
||||||
have this facility, but what it does have can work well for most use
|
|
||||||
cases we know about.
|
|
||||||
|
|
||||||
You would think that fluids or parameters would be Guile's answer for
|
You would think that fluids or parameters would be Guile's answer for
|
||||||
thread-local variables, since establishing a new fluid binding doesn't
|
thread-local variables, since establishing a new fluid binding doesn't
|
||||||
|
@ -191,26 +189,44 @@ bindings comes from a desire to isolate a binding from its setting in
|
||||||
unrelated threads, then fluids and parameters apply nicely.
|
unrelated threads, then fluids and parameters apply nicely.
|
||||||
|
|
||||||
On the other hand, if your use case is to prevent concurrent access to a
|
On the other hand, if your use case is to prevent concurrent access to a
|
||||||
value from multiple threads, then using fluids or parameters is not
|
value from multiple threads, then using vanilla fluids or parameters is
|
||||||
appropriate. In this case, our current suggestion is to use weak hash
|
not appropriate. For this purpose, Guile has @dfn{thread-local fluids}.
|
||||||
tables or object properties whose keys are thread objects. For example:
|
A fluid created with @code{make-thread-local-fluid} won't be captured by
|
||||||
|
@code{current-dynamic-state} and won't be propagated to new threads.
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} make-thread-local-fluid [dflt]
|
||||||
|
@deffnx {C Function} scm_make_thread_local_fluid (dflt)
|
||||||
|
Return a newly created fluid, whose initial value is @var{dflt}, or
|
||||||
|
@code{#f} if @var{dflt} is not given. Unlike fluids made with
|
||||||
|
@code{make-fluid}, thread local fluids are not captured by
|
||||||
|
@code{make-dynamic-state}. Similarly, a newly spawned child thread does
|
||||||
|
not inherit thread-local fluid values from the parent thread.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} fluid-thread-local? fluid
|
||||||
|
@deffnx {C Function} scm_fluid_thread_local_p (fluid)
|
||||||
|
Return @code{#t} if the fluid @var{fluid} is is thread-local, or
|
||||||
|
@code{#f} otherwise.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
@example
|
@example
|
||||||
(define (get-my-sensitive-data-structure)
|
(define %thread-local (make-thread-local-fluid))
|
||||||
...)
|
|
||||||
|
|
||||||
(define %thread-local (make-weak-key-hash-table))
|
(with-fluids ((%thread-local (compute-data)))
|
||||||
|
... (fluid-ref %thread-local) ...)
|
||||||
(define (current-thread-local)
|
|
||||||
(or (hashq-ref %thread-local (current-thread))
|
|
||||||
(let ((val (get-my-sensitive-data-structure)))
|
|
||||||
(hashq-set! %thread-local (current-thread) val)
|
|
||||||
val)))
|
|
||||||
@end example
|
@end example
|
||||||
|
|
||||||
It's not a terribly nice facility and perhaps we should have a better
|
You can also make a thread-local parameter out of a thread-local fluid
|
||||||
answer, like Racket's ``non-preserved thread cells''. Your input is
|
using the normal @code{fluid->parameter}:
|
||||||
very welcome; we look forward to hearing from your experience.
|
|
||||||
|
@example
|
||||||
|
(define param (fluid->parameter (make-thread-local-fluid)))
|
||||||
|
|
||||||
|
(parameterize ((param (compute-data)))
|
||||||
|
... (param) ...)
|
||||||
|
@end example
|
||||||
|
|
||||||
|
|
||||||
@node Asyncs
|
@node Asyncs
|
||||||
|
|
|
@ -91,6 +91,10 @@
|
||||||
table could share more state, as in an immutable weak array-mapped
|
table could share more state, as in an immutable weak array-mapped
|
||||||
hash trie or something, but we don't have such a data structure. */
|
hash trie or something, but we don't have such a data structure. */
|
||||||
|
|
||||||
|
#define FLUID_F_THREAD_LOCAL 0x100
|
||||||
|
#define SCM_I_FLUID_THREAD_LOCAL_P(x) \
|
||||||
|
(SCM_CELL_WORD_0 (x) & FLUID_F_THREAD_LOCAL)
|
||||||
|
|
||||||
static inline int
|
static inline int
|
||||||
is_dynamic_state (SCM x)
|
is_dynamic_state (SCM x)
|
||||||
{
|
{
|
||||||
|
@ -103,6 +107,8 @@ get_dynamic_state (SCM dynamic_state)
|
||||||
return SCM_CELL_OBJECT_1 (dynamic_state);
|
return SCM_CELL_OBJECT_1 (dynamic_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Precondition: It's OK to throw away any unflushed data in the current
|
||||||
|
cache. */
|
||||||
static inline void
|
static inline void
|
||||||
restore_dynamic_state (SCM saved, scm_t_dynamic_state *state)
|
restore_dynamic_state (SCM saved, scm_t_dynamic_state *state)
|
||||||
{
|
{
|
||||||
|
@ -133,9 +139,20 @@ save_dynamic_state (scm_t_dynamic_state *state)
|
||||||
struct scm_cache_entry *entry = &state->cache.entries[slot];
|
struct scm_cache_entry *entry = &state->cache.entries[slot];
|
||||||
SCM key = SCM_PACK (entry->key);
|
SCM key = SCM_PACK (entry->key);
|
||||||
SCM value = SCM_PACK (entry->value);
|
SCM value = SCM_PACK (entry->value);
|
||||||
if (entry->key &&
|
|
||||||
!scm_is_eq (scm_weak_table_refq (state->values, key, SCM_UNDEFINED),
|
if (!entry->key)
|
||||||
value))
|
continue;
|
||||||
|
if (SCM_I_FLUID_THREAD_LOCAL_P (key))
|
||||||
|
{
|
||||||
|
/* Because we don't include unflushed thread-local fluids in
|
||||||
|
the result, we need to flush them to the table so that
|
||||||
|
restore_dynamic_state can just throw away the current
|
||||||
|
cache. */
|
||||||
|
scm_hashq_set_x (state->thread_local_values, key, value);
|
||||||
|
}
|
||||||
|
else if (!scm_is_eq (scm_weak_table_refq (state->values, key,
|
||||||
|
SCM_UNDEFINED),
|
||||||
|
value))
|
||||||
{
|
{
|
||||||
if (state->has_aliased_values)
|
if (state->has_aliased_values)
|
||||||
saved = scm_acons (key, value, saved);
|
saved = scm_acons (key, value, saved);
|
||||||
|
@ -177,7 +194,10 @@ copy_value_table (SCM tab)
|
||||||
void
|
void
|
||||||
scm_i_fluid_print (SCM exp, SCM port, scm_print_state *pstate SCM_UNUSED)
|
scm_i_fluid_print (SCM exp, SCM port, scm_print_state *pstate SCM_UNUSED)
|
||||||
{
|
{
|
||||||
scm_puts ("#<fluid ", port);
|
if (SCM_I_FLUID_THREAD_LOCAL_P (exp))
|
||||||
|
scm_puts ("#<thread-local-fluid ", port);
|
||||||
|
else
|
||||||
|
scm_puts ("#<fluid ", port);
|
||||||
scm_intprint (SCM_UNPACK (exp), 16, port);
|
scm_intprint (SCM_UNPACK (exp), 16, port);
|
||||||
scm_putc ('>', port);
|
scm_putc ('>', port);
|
||||||
}
|
}
|
||||||
|
@ -196,15 +216,15 @@ scm_i_dynamic_state_print (SCM exp, SCM port, scm_print_state *pstate SCM_UNUSED
|
||||||
#define SCM_I_FLUID_DEFAULT(x) (SCM_CELL_OBJECT_1 (x))
|
#define SCM_I_FLUID_DEFAULT(x) (SCM_CELL_OBJECT_1 (x))
|
||||||
|
|
||||||
static SCM
|
static SCM
|
||||||
new_fluid (SCM init)
|
new_fluid (SCM init, scm_t_bits flags)
|
||||||
{
|
{
|
||||||
return scm_cell (scm_tc7_fluid, SCM_UNPACK (init));
|
return scm_cell (scm_tc7_fluid | flags, SCM_UNPACK (init));
|
||||||
}
|
}
|
||||||
|
|
||||||
SCM
|
SCM
|
||||||
scm_make_fluid (void)
|
scm_make_fluid (void)
|
||||||
{
|
{
|
||||||
return new_fluid (SCM_BOOL_F);
|
return new_fluid (SCM_BOOL_F, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
SCM_DEFINE (scm_make_fluid_with_default, "make-fluid", 0, 1, 0,
|
SCM_DEFINE (scm_make_fluid_with_default, "make-fluid", 0, 1, 0,
|
||||||
|
@ -219,7 +239,7 @@ SCM_DEFINE (scm_make_fluid_with_default, "make-fluid", 0, 1, 0,
|
||||||
"with its own dynamic state, you can use fluids for thread local storage.")
|
"with its own dynamic state, you can use fluids for thread local storage.")
|
||||||
#define FUNC_NAME s_scm_make_fluid_with_default
|
#define FUNC_NAME s_scm_make_fluid_with_default
|
||||||
{
|
{
|
||||||
return new_fluid (SCM_UNBNDP (dflt) ? SCM_BOOL_F : dflt);
|
return new_fluid (SCM_UNBNDP (dflt) ? SCM_BOOL_F : dflt, 0);
|
||||||
}
|
}
|
||||||
#undef FUNC_NAME
|
#undef FUNC_NAME
|
||||||
|
|
||||||
|
@ -228,7 +248,22 @@ SCM_DEFINE (scm_make_unbound_fluid, "make-unbound-fluid", 0, 0, 0,
|
||||||
"Make a fluid that is initially unbound.")
|
"Make a fluid that is initially unbound.")
|
||||||
#define FUNC_NAME s_scm_make_unbound_fluid
|
#define FUNC_NAME s_scm_make_unbound_fluid
|
||||||
{
|
{
|
||||||
return new_fluid (SCM_UNDEFINED);
|
return new_fluid (SCM_UNDEFINED, 0);
|
||||||
|
}
|
||||||
|
#undef FUNC_NAME
|
||||||
|
|
||||||
|
SCM_DEFINE (scm_make_thread_local_fluid, "make-thread-local-fluid", 0, 1, 0,
|
||||||
|
(SCM dflt),
|
||||||
|
"Return a newly created fluid, whose initial value is @var{dflt},\n"
|
||||||
|
"or @code{#f} if @var{dflt} is not given. Unlike fluids made\n"
|
||||||
|
"with @code{make-fluid}, thread local fluids are not captured\n"
|
||||||
|
"by @code{make-dynamic-state}. Similarly, a newly spawned\n"
|
||||||
|
"child thread does not inherit thread-local fluid values from\n"
|
||||||
|
"the parent thread.")
|
||||||
|
#define FUNC_NAME s_scm_make_thread_local_fluid
|
||||||
|
{
|
||||||
|
return new_fluid (SCM_UNBNDP (dflt) ? SCM_BOOL_F : dflt,
|
||||||
|
FLUID_F_THREAD_LOCAL);
|
||||||
}
|
}
|
||||||
#undef FUNC_NAME
|
#undef FUNC_NAME
|
||||||
|
|
||||||
|
@ -242,6 +277,17 @@ SCM_DEFINE (scm_fluid_p, "fluid?", 1, 0, 0,
|
||||||
}
|
}
|
||||||
#undef FUNC_NAME
|
#undef FUNC_NAME
|
||||||
|
|
||||||
|
SCM_DEFINE (scm_fluid_thread_local_p, "fluid-thread-local?", 1, 0, 0,
|
||||||
|
(SCM fluid),
|
||||||
|
"Return @code{#t} if the fluid @var{fluid} is is thread local,\n"
|
||||||
|
"or @code{#f} otherwise.")
|
||||||
|
#define FUNC_NAME s_scm_fluid_thread_local_p
|
||||||
|
{
|
||||||
|
SCM_VALIDATE_FLUID (1, fluid);
|
||||||
|
return scm_from_bool (SCM_I_FLUID_THREAD_LOCAL_P (fluid));
|
||||||
|
}
|
||||||
|
#undef FUNC_NAME
|
||||||
|
|
||||||
int
|
int
|
||||||
scm_is_fluid (SCM obj)
|
scm_is_fluid (SCM obj)
|
||||||
{
|
{
|
||||||
|
@ -268,6 +314,12 @@ fluid_set_x (scm_t_dynamic_state *dynamic_state, SCM fluid, SCM value)
|
||||||
fluid = SCM_PACK (evicted.key);
|
fluid = SCM_PACK (evicted.key);
|
||||||
value = SCM_PACK (evicted.value);
|
value = SCM_PACK (evicted.value);
|
||||||
|
|
||||||
|
if (SCM_I_FLUID_THREAD_LOCAL_P (fluid))
|
||||||
|
{
|
||||||
|
scm_hashq_set_x (dynamic_state->thread_local_values, fluid, value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (dynamic_state->has_aliased_values)
|
if (dynamic_state->has_aliased_values)
|
||||||
{
|
{
|
||||||
if (scm_is_eq (scm_weak_table_refq (dynamic_state->values,
|
if (scm_is_eq (scm_weak_table_refq (dynamic_state->values,
|
||||||
|
@ -294,7 +346,12 @@ fluid_ref (scm_t_dynamic_state *dynamic_state, SCM fluid)
|
||||||
val = SCM_PACK (entry->value);
|
val = SCM_PACK (entry->value);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
val = scm_weak_table_refq (dynamic_state->values, fluid, SCM_UNDEFINED);
|
if (SCM_I_FLUID_THREAD_LOCAL_P (fluid))
|
||||||
|
val = scm_hashq_ref (dynamic_state->thread_local_values, fluid,
|
||||||
|
SCM_UNDEFINED);
|
||||||
|
else
|
||||||
|
val = scm_weak_table_refq (dynamic_state->values, fluid,
|
||||||
|
SCM_UNDEFINED);
|
||||||
|
|
||||||
if (SCM_UNBNDP (val))
|
if (SCM_UNBNDP (val))
|
||||||
val = SCM_I_FLUID_DEFAULT (fluid);
|
val = SCM_I_FLUID_DEFAULT (fluid);
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
|
|
||||||
struct scm_dynamic_state
|
struct scm_dynamic_state
|
||||||
{
|
{
|
||||||
|
SCM thread_local_values;
|
||||||
SCM values;
|
SCM values;
|
||||||
uint8_t has_aliased_values;
|
uint8_t has_aliased_values;
|
||||||
struct scm_cache cache;
|
struct scm_cache cache;
|
||||||
|
@ -53,8 +54,10 @@ struct scm_dynamic_state
|
||||||
SCM_API SCM scm_make_fluid (void);
|
SCM_API SCM scm_make_fluid (void);
|
||||||
SCM_API SCM scm_make_fluid_with_default (SCM dflt);
|
SCM_API SCM scm_make_fluid_with_default (SCM dflt);
|
||||||
SCM_API SCM scm_make_unbound_fluid (void);
|
SCM_API SCM scm_make_unbound_fluid (void);
|
||||||
|
SCM_API SCM scm_make_thread_local_fluid (SCM dflt);
|
||||||
SCM_API int scm_is_fluid (SCM obj);
|
SCM_API int scm_is_fluid (SCM obj);
|
||||||
SCM_API SCM scm_fluid_p (SCM fl);
|
SCM_API SCM scm_fluid_p (SCM fl);
|
||||||
|
SCM_API SCM scm_fluid_thread_local_p (SCM fluid);
|
||||||
SCM_API SCM scm_fluid_ref (SCM fluid);
|
SCM_API SCM scm_fluid_ref (SCM fluid);
|
||||||
SCM_API SCM scm_fluid_ref_star (SCM fluid, SCM depth);
|
SCM_API SCM scm_fluid_ref_star (SCM fluid, SCM depth);
|
||||||
SCM_API SCM scm_fluid_set_x (SCM fluid, SCM value);
|
SCM_API SCM scm_fluid_set_x (SCM fluid, SCM value);
|
||||||
|
|
|
@ -459,7 +459,7 @@ SCM_DEFINE (scm_stack_length, "stack-length", 1, 0, 0,
|
||||||
void
|
void
|
||||||
scm_init_stacks ()
|
scm_init_stacks ()
|
||||||
{
|
{
|
||||||
scm_sys_stacks = scm_make_fluid ();
|
scm_sys_stacks = scm_make_thread_local_fluid (SCM_BOOL_F);
|
||||||
scm_c_define ("%stacks", scm_sys_stacks);
|
scm_c_define ("%stacks", scm_sys_stacks);
|
||||||
|
|
||||||
scm_stack_type = scm_make_vtable (scm_from_locale_string (SCM_STACK_LAYOUT),
|
scm_stack_type = scm_make_vtable (scm_from_locale_string (SCM_STACK_LAYOUT),
|
||||||
|
|
|
@ -458,6 +458,7 @@ guilify_self_2 (SCM dynamic_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
t->dynamic_state = scm_gc_typed_calloc (scm_t_dynamic_state);
|
t->dynamic_state = scm_gc_typed_calloc (scm_t_dynamic_state);
|
||||||
|
t->dynamic_state->thread_local_values = scm_c_make_hash_table (0);
|
||||||
scm_set_current_dynamic_state (dynamic_state);
|
scm_set_current_dynamic_state (dynamic_state);
|
||||||
|
|
||||||
t->dynstack.base = scm_gc_malloc (16 * sizeof (scm_t_bits), "dynstack");
|
t->dynstack.base = scm_gc_malloc (16 * sizeof (scm_t_bits), "dynstack");
|
||||||
|
|
|
@ -648,7 +648,7 @@ scm_init_throw ()
|
||||||
tc16_catch_closure = scm_make_smob_type ("catch-closure", 0);
|
tc16_catch_closure = scm_make_smob_type ("catch-closure", 0);
|
||||||
scm_set_smob_apply (tc16_catch_closure, apply_catch_closure, 0, 0, 1);
|
scm_set_smob_apply (tc16_catch_closure, apply_catch_closure, 0, 0, 1);
|
||||||
|
|
||||||
exception_handler_fluid = scm_make_fluid_with_default (SCM_BOOL_F);
|
exception_handler_fluid = scm_make_thread_local_fluid (SCM_BOOL_F);
|
||||||
/* This binding is later removed when the Scheme definitions of catch,
|
/* This binding is later removed when the Scheme definitions of catch,
|
||||||
throw, and with-throw-handler are created in boot-9.scm. */
|
throw, and with-throw-handler are created in boot-9.scm. */
|
||||||
scm_c_define ("%exception-handler", exception_handler_fluid);
|
scm_c_define ("%exception-handler", exception_handler_fluid);
|
||||||
|
|
|
@ -260,4 +260,10 @@
|
||||||
(fluid-ref fluid))))
|
(fluid-ref fluid))))
|
||||||
(lambda (k) k))))
|
(lambda (k) k))))
|
||||||
(and (eqv? (fluid-ref fluid) #f)
|
(and (eqv? (fluid-ref fluid) #f)
|
||||||
(eqv? (k) #t))))))
|
(eqv? (k) #t)))))
|
||||||
|
|
||||||
|
(pass-if "exception handler not captured"
|
||||||
|
(let ((state (catch #t (lambda () (current-dynamic-state)) error)))
|
||||||
|
(catch #t
|
||||||
|
(lambda () (with-dynamic-state state (/ 1 0)))
|
||||||
|
(lambda _ #t)))))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue