TLS variable for only one thread

Jonathan M Davis newsgroup.d at jmdavisprog.com
Thu Feb 20 07:45:13 UTC 2025


On Wednesday, February 19, 2025 11:24:36 PM MST IchorDev via Digitalmars-d-learn wrote:
> On Saturday, 15 February 2025 at 10:09:39 UTC, Jonathan M Davis
> wrote:
> > I think that you need to be clearer about what you're trying to
> > do. If a module-level variable is not shared, __gshared, or
> > immutable, then each thread gets a completely separate
> > variable, and no other thread has access to that object unless
> > you do something to pass it to another thread (which would
> > involve casting to shared or immutable and calling and then
> > using something like std.concurrency to pass it across), and
> > the only way to give another thread access to the variable
> > itself would be to take its address and cast the pointer to
> > shared or immutable to be able to pass it across threads. The
> > variable itself is restricted to a single thread, so it's
> > already the case that no other threads have access to a
> > non-shared variable.
>
> Perhaps my use of the term TLS was unhelpful. I never studied
> multi-threading at the architectural level, so forgive me if my
> understanding is a bit fuzzy.

Thread-local storage is an OS thing, but it's often the case that folks use
the term thread-local to refer to D types which are not shared, because
other threads don't have access to them. So, talking about thread-local can
become confusing depending on who's talking and what they care about, but
TLS specifically refers to the OS thing, since D's type system doesn't do
anything special with storage to make objects thread-local. It just enforces
that only one thread can access them unless you go to the effort of casting
to be able to pass them across threads (in which case, it's up to you to
ensure that thread-safety isn't violated and that multiple threads don't
access the object at once).

> Let's say I have some important singleton class instance on
> thread A, but thread B doesn't need it, so it isn't `shared`:
> ```d
> SingletonObject foo;
> ```
> However, this means that thread B has its own version of `foo`,
> right? Which surely means:
> 1. Thread B is wasting space by storing a (null) pointer for its
> version of `foo` at all times; and

True, but we're talking about one class reference per thread, which is
negligible.

> 2. B could potentially start using its version of `foo`, which I
> really do not want.

Well, if that's an issue for some reason, then you need to have a shared
object and deal with the issues that come with an object being accessible
from multiple threads.

So, if you have a module-level variable, either you have a thread-local /
non-shared one, and each thread gets its own copy, or you have a shared one,
and there is only one copy, but then you have to worry about thread-safety.
Which is better depends on what you're doing, but unless you're specifically
trying to share data across threads, it's usually the case that it's better
to just have separate copies per thread and avoid having to deal with
mutexes or atomics or whatnot.

Now, if your object is immutable, then it's implicitly shared, because it
can't be mutated and thus can be safely shared across threads. So, in some
cases, that would be a good solution, but of course, that only works if you
object doesn't ever need to be mutated.

But if you're looking to somehow have a variable that only exists on one
thread and no other thread can see it, then I'm sorry, but that's not a
thing with module-level or static variables in D. You could create an object
and pass it around everywhere, but if you have a module-level or static
variable, your two options are shared and thread-local (with thread-local
meaning that each thread gets its own copy) - though if the variable isn't
shared, and you don't initialize it with a static constructor, then each
thread will have a variable which is set to its init value (which would be
null with a class reference if it's not directly initialized), so you
wouldn't be allocating a class object per thread unless you explicitly chose
to. You do end up with a separate class reference per thread though even if
it's never set to anything other than null.

There's also __gshared, which is intended for binding to C global variables,
but all it's really doing is creating a shared variable that is typed as
thread-local, so you have to be _really_ careful with those. You're
basically getting a shared variable where shared has been cast away (making
them inherently @system, though I'm not sure if the type system currently
treats them that way like it should, since @system variables are pretty
new). So, every thread has access to the same object, but there's no
protection whatsoever against accessing it from multiple threads, and it
certainly wouldn't help making it so that only one thread has access to the
variable.

Based on what you've said you're trying to do, I'm guessing that your two
best options are either to have a thread-local module-level or static
variable and just live with the fact that each thread gets its own copy - or
to pass around the object in question wherever it's needed instead of having
a module-level or static variable for it. The latter case could be annoying
because of all of the extra function parameters required, but it does
provide the guarantee that no other thread is going to have that object, and
you aren't taking up the 64 bits for the class reference on threads that
don't need it.

- Jonathan M Davis





More information about the Digitalmars-d-learn mailing list