Thread overview
TLS variable for only one thread
Feb 15
IchorDev
4 days ago
IchorDev
February 15

If I have a module-level variable, each thread sees a different value for it. But how would I create a module-level variable that can only be accessed by one thread? Do I have to give it its own module?

February 15
On Saturday, February 15, 2025 12:53:17 AM MST IchorDev via Digitalmars-d-learn wrote:
> If I have a module-level variable, each thread sees a different value for it. But how would I create a module-level variable that can only be accessed by one thread? Do I have to give it its own module?

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.

Now, if you mean that you want the object to literally be in thread-local storage, the language has no support for that, and I don't know quite what you'd buy by having that, since it's already guaranteed that variables aren't shared across threads unless you're using shared, __gshared, or immutable to make it happen. You'd likely to have to use whatever OS facilities are provided in C to deal with TLS. Either way, D itself doesn't do anything with TLS, much as we often talk about non-shared variables being thread-local.

- Jonathan M Davis



4 days ago

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.
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:

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
  2. B could potentially start using its version of foo, which I really do not want.
4 days ago
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