# Sharp edge with \`Threads.threadid()\` and task migration

**URL:** <https://discourse.julialang.org/t/sharp-edge-with-threads-threadid-and-task-migration/124550>\
**Category:** General Usage\
**Tags:** multithreading\
**Created:** [January 8, 2025, 5:32pm UTC](https://discourse.julialang.org/t/sharp-edge-with-threads-threadid-and-task-migration/124550 "2025-01-08T17:32:21Z")\
**Posts on this page:** 20\
**Page:** 3

<div class="post-metadata">

**Author:** ![Andrea\_Pagnani](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/andrea_pagnani/32/3136_2.png) [@Andrea\_Pagnani](https://discourse.julialang.org/u/Andrea_Pagnani)\
**Post date:** [January 9, 2025, 4:23pm UTC](https://discourse.julialang.org/t/sharp-edge-with-threads-threadid-and-task-migration/124550/41 "2025-01-09T16:23:10Z")

</div>

Dearests,

I somehow sympathize with @MilesCranmer here about deprecating the `threadid()` construct.

Actually, my understanding is that using `threadid()` is safe provided that one uses the static scheduler (please correct me if I am wrong because this is the strategy I used to mitigate the problem in two of my packages)

```julia
Threads.@threads :static for a in 1:N
   counter[Threads.threadid()] = ...
end

```

A possibility would be to raise a warning/deprecation only if one is NOT using the `:static` scheduler (again, provided that `:static` makes the use of `threadid()` safe). I do not know how feasible would be to raise a conditional warning depending on the choice of the scheduler however.

---

<div class="post-metadata">

**Author:** ![adienes](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/adienes/32/37459_2.png) [@adienes](https://discourse.julialang.org/u/adienes)\
**Post date:** [January 9, 2025, 4:25pm UTC](https://discourse.julialang.org/t/sharp-edge-with-threads-threadid-and-task-migration/124550/42 "2025-01-09T16:25:12Z")

</div>

> [@MilesCranmer](#):
>
> the old API

> [@MilesCranmer](#):
>
> APIs have _inertia_ to them.

but it was never the API 😬. I understand there were official sources recommending this pattern, but nonetheless this is not a breaking change in API we’re talking about. it’s simply buggy code.

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [January 9, 2025, 4:27pm UTC](https://discourse.julialang.org/t/sharp-edge-with-threads-threadid-and-task-migration/124550/43 "2025-01-09T16:27:28Z")

</div>

> [@adienes](#):
>
> but it was never the API 😬. I understand there were official sources recommending this pattern

What’s the difference?

---

<div class="post-metadata">

**Author:** ![adienes](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/adienes/32/37459_2.png) [@adienes](https://discourse.julialang.org/u/adienes)\
**Post date:** [January 9, 2025, 4:29pm UTC](https://discourse.julialang.org/t/sharp-edge-with-threads-threadid-and-task-migration/124550/44 "2025-01-09T16:29:18Z")

</div>

one is in the docs and looks more like a reference manual. the other is in blog posts and looks more like a TA showing you how to code

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [January 9, 2025, 4:30pm UTC](https://discourse.julialang.org/t/sharp-edge-with-threads-threadid-and-task-migration/124550/45 "2025-01-09T16:30:06Z")

</div>

Except that it was a blog post by one of the creators of the language

---

<div class="post-metadata">

**Author:** ![adienes](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/adienes/32/37459_2.png) [@adienes](https://discourse.julialang.org/u/adienes)\
**Post date:** [January 9, 2025, 4:30pm UTC](https://discourse.julialang.org/t/sharp-edge-with-threads-threadid-and-task-migration/124550/46 "2025-01-09T16:30:43Z")

</div>

yes, which makes it extra unfortunate that that post contained errors pertaining to the true API.

---

<div class="post-metadata">

**Author:** ![sgaure](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sgaure/32/14779_2.png) [@sgaure](https://discourse.julialang.org/u/sgaure)\
**Post date:** [January 9, 2025, 4:31pm UTC](https://discourse.julialang.org/t/sharp-edge-with-threads-threadid-and-task-migration/124550/47 "2025-01-09T16:31:22Z")

</div>

> [@MilesCranmer](#):
>
> It’s not so much about atomics, it’s that `Threads.threadid()` can change in a single scope

Nah. Without the atomic, the program may fail, with the atomic it can’t. I suppose an atomic was used for that reason.

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [January 9, 2025, 4:32pm UTC](https://discourse.julialang.org/t/sharp-edge-with-threads-threadid-and-task-migration/124550/48 "2025-01-09T16:32:15Z")

</div>

By “true API” do you mean this docstring? This is on 1.7.

```julia
help?> Threads.threadid()
  Threads.threadid()

  Get the ID number of the current thread of execution. The master thread has ID 1.

```

So, based on these two sentences, you think the average user is supposed to infer that the creator of Julia was incorrect on an official blogpost?

---

<div class="post-metadata">

**Author:** ![adienes](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/adienes/32/37459_2.png) [@adienes](https://discourse.julialang.org/u/adienes)\
**Post date:** [January 9, 2025, 4:41pm UTC](https://discourse.julialang.org/t/sharp-edge-with-threads-threadid-and-task-migration/124550/49 "2025-01-09T16:41:30Z")

</div>

I don’t mean for this to become heated. I don’t think you’re crazy for wanting to deprecate `threadid` I just don’t happen to agree

> you think the average user is supposed to infer that the creator of Julia was incorrect on an official blogpost?

absolutely not, I never said that.

let me list the things I agree with you on

- The docstring of `threadid` (old and new alike) is rather curt & vague. especially before the note was added it would have been pretty hard for anybody without a good understanding of Julia’s threading model to know what to assume is meant exactly
- multithreaded code, in general, is really complicated and easy to get wrong especially in the absence of tools that can hold the users’ hands
- once code patterns are out there, they are very sticky and it can be very hard to effect community-wide changes
- recommended usage patterns of `threadid`, from official sources, actively deceived users into writing buggy code
- the previous fact is really REALLY unfortunate

All these facts are pretty indisputable. AND.

all that being said, I do not share your conclusion that deprecating `threadid` is a good course of action. again that’s just my opinion and it’s possible I’m in the minority here, but I’d rather not remove a (now) correctly-documented and correctly-implemented tool on the basis that its usage was previously taught incorrectly.

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [January 9, 2025, 5:36pm UTC](https://discourse.julialang.org/t/sharp-edge-with-threads-threadid-and-task-migration/124550/50 "2025-01-09T17:36:16Z")

</div>

Sorry if you felt I responded too strongly, that was not my intention. I was attempting to point out in response to your comment

> [@adienes](#):
>
> but it was never the API 😬.

that this might not paint an accurate picture of pre-1.8 sources.

---

<div class="post-metadata">

**Author:** ![mbauman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mbauman/32/31082_2.png) [@mbauman](https://discourse.julialang.org/u/mbauman)\
**Post date:** [January 9, 2025, 5:43pm UTC](https://discourse.julialang.org/t/sharp-edge-with-threads-threadid-and-task-migration/124550/51 "2025-01-09T17:43:12Z")

</div>

I don’t think anyone here is disagreeing about the general state of affairs. It is indeed very unfortunate and truly is well described as a trolley problem. And just like any good trolley problem, everyone’s perspective on how to best throw _any_ switch will vary based upon what you’re able to see, which side of the tracks you personally find yourself tied to, your perspective on the other downstream effects, and where you want to find the trolley at the end of the day. It’s a tradeoff and your weighings of the outcomes may vary. It’s valuable to also understand that _others’_ weighings of the same situation may be different than yours, even if everyone is standing at the exact same spot with the exact same visibility.

Go’s choice here to forbid even looking at thread ids is quite fitting with its own worldview.

Unlike trolley problems, we can also build new tracks — we’re not limited to a binary choice here. Even better: some options aren’t even mutually exclusive.

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [January 9, 2025, 6:23pm UTC](https://discourse.julialang.org/t/sharp-edge-with-threads-threadid-and-task-migration/124550/52 "2025-01-09T18:23:15Z")

</div>

Thanks for the great summary. And glad to hear you appreciated the metaphor 🙂

Do you have any ideas for the “new tracks” here? In my view the best overall option (though somewhat controversial) is the logic [here](https://discourse.julialang.org/t/sharp-edge-with-threads-threadid-and-task-migration/124550/29). A warning in the linter could also be a decent start since it is more aggressive and “in your face” than a blogpost or docstring update – it would indeed start pushing this into common Julia knowledge. Though it still wouldn’t warn on legacy code, I do think it might save a fair chunk of all _future_ casualties though.

---

<div class="post-metadata">

**Author:** ![danielwe](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielwe/32/35657_2.png) [@danielwe](https://discourse.julialang.org/u/danielwe)\
**Post date:** [January 9, 2025, 6:38pm UTC](https://discourse.julialang.org/t/sharp-edge-with-threads-threadid-and-task-migration/124550/53 "2025-01-09T18:38:26Z")

</div>

> [@MilesCranmer](#):
>
> apparently bad habit of using lots of `@spawn`

Just want to emphasize the nuance here that `@spawn` is a perfectly legitimate tool and not a code smell on its own, but it’s low-level, and if you’re using it to multithread a loop you should probably combine it with manual chunking/work scheduling (e.g., using ChunkSplitters.jl). Though as @ericphanson pointed out, this is less important if each iteration takes a long time anyway (say, idk, several tens of microseconds or more).

Or maybe we can infer another heuristic from this thread: if you’re annoyed that `thread_local_storage()` can’t persist/transfer between tasks, perhaps you should reduce the number of tasks and give each of them more work.

For commonly used patterns, OhMyThreads.jl is the go-to library for pre-packaged variants so you don’t have to write the boilerplate yourself.

---

<div class="post-metadata">

**Author:** ![mbauman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mbauman/32/31082_2.png) [@mbauman](https://discourse.julialang.org/u/mbauman)\
**Post date:** [January 9, 2025, 7:49pm UTC](https://discourse.julialang.org/t/sharp-edge-with-threads-threadid-and-task-migration/124550/54 "2025-01-09T19:49:48Z")

</div>

> [@MilesCranmer](#):
>
> Do you have any ideas for the “new tracks” here? In my view the best overall option (though somewhat controversial) is the logic [here](https://discourse.julialang.org/t/sharp-edge-with-threads-threadid-and-task-migration/124550/29).

Yes, StaticLint.jl was my main thought and I’m sure it would be quite noncontroversial. Your suggestion might work, but that’s why I posted my archaeology note above; straight deprecations have already been tried. The core question is if this would reduce false-positives enough to make the evaluation any different than the last times this has been tried. I don’t know the answer, don’t have a strong opinion, and don’t hold power here.

---

<div class="post-metadata">

**Author:** ![danielwe](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielwe/32/35657_2.png) [@danielwe](https://discourse.julialang.org/u/danielwe)\
**Post date:** [January 9, 2025, 8:38pm UTC](https://discourse.julialang.org/t/sharp-edge-with-threads-threadid-and-task-migration/124550/55 "2025-01-09T20:38:28Z")

</div>

> [@Mason](#):
>
> `Threads.@threads` is quite bad

I understand this sentiment to the extent that you feel like `@threads` implicitly encourages incorrect `threadid()`-based caching patterns that people need to be steered away from. However, I haven’t found a better tool for parallelizing simple loops that don’t need thread-safe cache. It doesn’t add a dependency, and it has lower overhead than anything I’ve tried except Polyester.jl (which has its own issues with composability). I don’t think a blanket warning against `@threads` is warranted, especially if it drives people towards naive use of `@spawn` with loops instead.

I think the appropriate admonitions are approximately:

- Never use `threadid()`
- If `@threads` works for you, great, if not, use OhMyThreads.jl
- If you’re tempted to use `@spawn` with loops, you should probably try OhMyThreads.jl first
- If you still want to use `@spawn` with loops, read the [blog post](https://julialang.org/blog/2023/07/PSA-dont-use-threadid/) first, and probably also the ChunkSplitters.jl docs
- Outside the context of parallelizing loops, use `@spawn` to your heart’s content the way you would use `@async`

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [January 9, 2025, 8:54pm UTC](https://discourse.julialang.org/t/sharp-edge-with-threads-threadid-and-task-migration/124550/56 "2025-01-09T20:54:54Z")

</div>

> [@danielwe](#):
>
> I understand this sentiment to the extent that you feel like `@threads` implicitly encourages incorrect `threadid()`-based caching patterns that people need to be steered away from.

For what it’s worth, my point isn’t that people shouldn’t ever use `@threads`. My point is just that it’s a bad API. If it does what you need to do, then great, but it’s generally limiting, and encourages bad practices which is frustrating.

---

<div class="post-metadata">

**Author:** ![sgaure](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sgaure/32/14779_2.png) [@sgaure](https://discourse.julialang.org/u/sgaure)\
**Post date:** [January 10, 2025, 9:24am UTC](https://discourse.julialang.org/t/sharp-edge-with-threads-threadid-and-task-migration/124550/57 "2025-01-10T09:24:45Z")

</div>

I think this discussion boils down to whom the language should accomodate. Personally, I’m perfectly fine with a language which allows me to write buggy code. I’ve been writing parallel code since VMS 5 (ca. 1988), in C, pascal, BLISS-32, you name it, and believe that anyone writing parallel code anyway must learn some basic synchronization techinques. In julia’s case, also how tasks and threads interact, or may interact in the future. Automatic parallelization and/or synchronization has been a pipe dream for 40 years (since the [Connection Machine](https://en.wikipedia.org/wiki/Connection_Machine), or before). Babysitting the developer by blocking potentially dangerous practices won’t help anyone.

---

<div class="post-metadata">

**Author:** ![StefanKarpinski](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefankarpinski/32/24_2.png) [@StefanKarpinski](https://discourse.julialang.org/u/StefanKarpinski)\
**Post date:** [January 10, 2025, 3:00pm UTC](https://discourse.julialang.org/t/sharp-edge-with-threads-threadid-and-task-migration/124550/58 "2025-01-10T15:00:00Z")

</div>

> [@sgaure](#):
>
> Babysitting the developer by blocking potentially dangerous practices won’t help anyone.

This is kind of a dismissive characterization of the progress various languages have made in the past decades towards making it harder to _accidentally_ write code that doesn’t work. Even though Julia very much has an approach of _letting_ people do anything they need to, we try very hard to avoid making easy to do the wrong thing unintentionally.

This is a kind of unfortunate API in that it’s easy to misuse, but there’s no simple change that makes it that much harder to misuse—asking for the ID of a thread or a task just isn’t a good fit for the way threading works in Julia. We let you ask for the thread ID because it is a thing you could potentially want to know, but since tasks can migrate threads, it doesn’t work the way many people expect it to.

---

<div class="post-metadata">

**Author:** ![tt1234567](https://avatars.discourse-cdn.com/v4/letter/t/dec6dc/32.png) [@tt1234567](https://discourse.julialang.org/u/tt1234567)\
**Post date:** [January 10, 2025, 3:58pm UTC](https://discourse.julialang.org/t/sharp-edge-with-threads-threadid-and-task-migration/124550/59 "2025-01-10T15:58:14Z")

</div>

> [@Mason](#):
>
> …  
> Yes, `Threads.@threads` is quite bad. Working with tasks is nice if you know what you’re doing and are avoiding things like insane levels of oversubscription, and feel like re-writing a lot of code.
> 
> Otherwise, I strongly recommend using something like OhMyThreads.jl.
> 
> > [@MilesCranmer](#):
> >
> > Why?
> 
> It’s very very very inefficient.

Sorry to jump into the middle of this, but is `Threads.@threads` really inefficient? I’ve been using a bit recently but didn’t realise it was that bad. Is `OhMyThreads.jl` a direct replacement and more efficient?

---

<div class="post-metadata">

**Author:** ![danielwe](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielwe/32/35657_2.png) [@danielwe](https://discourse.julialang.org/u/danielwe)\
**Post date:** [January 10, 2025, 4:34pm UTC](https://discourse.julialang.org/t/sharp-edge-with-threads-threadid-and-task-migration/124550/60 "2025-01-10T16:34:44Z")

</div>

> [@tt1234567](#):
>
> is `Threads.@threads` really inefficient

No, `Threads.@threads` is not inefficient, actually quite the opposite. It may be flawed design-wise, but performance-wise it has less overhead than most alternatives.

The pattern @Mason is saying it’s “very very very inefficient”, is the following:

> [@Mason](#):
>
> You should basically never create `n` separate tasks for a threaded loop over `n` items unless you statically know `n` is small.

In code:

```julia
@sync for i in 1:huge_number
    Threads.@spawn begin
        small_fast_loop_body(i)
    end
end

```

_This_ is very very very inefficient.

[Previous page](https://discourse.julialang.org/t/sharp-edge-with-threads-threadid-and-task-migration/124550.md?page=2)

[Next page](https://discourse.julialang.org/t/sharp-edge-with-threads-threadid-and-task-migration/124550.md?page=4)
