# Behavior of threads

**URL:** https://discourse.julialang.org/t/behavior-of-threads/95769
**Category:** General Usage
**Tags:** multithreading
**Created:** [March 8, 2023, 9:07pm UTC](https://discourse.julialang.org/t/behavior-of-threads/95769 "2023-03-08T21:07:33Z")
**Posts on this page:** 20
**Page:** 1

<div class="post-metadata">

### Author: ![PetrKryslUCSD](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/petrkryslucsd/32/215825_2.png) [@PetrKryslUCSD](https://discourse.julialang.org/u/PetrKryslUCSD)
#### Post date: [March 8, 2023, 9:07pm UTC](https://discourse.julialang.org/t/behavior-of-threads/95769/1 "2023-03-08T21:07:33Z")

</div>

The manual states:

> The `@threads` macro executes the loop body in an unspecified order and potentially concurrently. It does not specify the exact assignments of the tasks and the worker threads. The assignments can be different for each execution. The loop body code (including any code transitively called from it) must not make any assumptions about the distribution of iterations to tasks or the worker thread in which they are executed. The loop body for each iteration must be able to make forward progress independent of other iterations and be free from data races. As such, invalid synchronizations across iterations may deadlock while unsynchronized memory accesses may result in undefined behavior.

What I wonder is: can one make an assumption that when a function gets called within a loop body, the entire function body will be executed by single thread?

---

<div class="post-metadata">

### Author: ![Elrod](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/elrod/32/22461_2.png) [@Elrod](https://discourse.julialang.org/u/Elrod)
#### Post date: [March 8, 2023, 10:52pm UTC](https://discourse.julialang.org/t/behavior-of-threads/95769/2 "2023-03-08T22:52:29Z")

</div>

> [@PetrKryslUCSD](#):
>
> What I wonder is: can one make an assumption that when a function gets called with an a loop body, the entire function body will be executed by single thread?

No.

---

<div class="post-metadata">

### Author: ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)
#### Post date: [March 8, 2023, 11:29pm UTC](https://discourse.julialang.org/t/behavior-of-threads/95769/3 "2023-03-08T23:29:55Z")

</div>

> [@PetrKryslUCSD](#):
>
> What I wonder is: can one make an assumption that when a function gets called with an a loop body, the entire function body will be executed by single thread?

For the function in question, if you do not use `@threads` on the loop that calls it, can you know that the entire function body will be executed by a single thread?

---

<div class="post-metadata">

### Author: ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)
#### Post date: [March 8, 2023, 11:32pm UTC](https://discourse.julialang.org/t/behavior-of-threads/95769/4 "2023-03-08T23:32:50Z")

</div>

The function could use `@threads` internally, not to mention that due to task preemption, the task executing the loop could move between threads.

---

<div class="post-metadata">

### Author: ![PetrKryslUCSD](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/petrkryslucsd/32/215825_2.png) [@PetrKryslUCSD](https://discourse.julialang.org/u/PetrKryslUCSD)
#### Post date: [March 8, 2023, 11:52pm UTC](https://discourse.julialang.org/t/behavior-of-threads/95769/5 "2023-03-08T23:52:40Z")

</div>

Good points, everyone. To make my concern concrete: Consider the function

```julia
function f(a)
    th = Base.Threads.threadid()
    ... some code
    a[th] = 1
    ... some code
    th = Base.Threads.threadid()
    return a[th] == 1 ? 5 * a[th] : 0
end

```

This function takes an argument which is supposed to be a thread-private  
storage. Then use the function inside this threaded loop:

```julia
a = fill(0, Base.Threads.nthreads())
Base.Threads.@threads for th in 1:Base.Threads.nthreads()
    b = f(a)
end

```

Please never mind that the code doesn’t make much sense.  
My question is, is it possible that the `th` in the line `a[th] = 1` is not the same `th` as in the return line?  
Because if that is possible, then some other copy of this executing function  
could refer to the “same” `th`, thereby breaking the private storage.

---

<div class="post-metadata">

### Author: ![PetrKryslUCSD](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/petrkryslucsd/32/215825_2.png) [@PetrKryslUCSD](https://discourse.julialang.org/u/PetrKryslUCSD)
#### Post date: [March 9, 2023, 12:23am UTC](https://discourse.julialang.org/t/behavior-of-threads/95769/6 "2023-03-09T00:23:00Z")

</div>

I think I chanced upon the solution: the argument `:static` for the `@threads` macro  
should guarantee what I want.

```julia
a = fill(0, Base.Threads.nthreads())
Base.Threads.@threads :static for th in 1:Base.Threads.nthreads()
    b = f(a)
end

```

---

<div class="post-metadata">

### Author: ![PetrKryslUCSD](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/petrkryslucsd/32/215825_2.png) [@PetrKryslUCSD](https://discourse.julialang.org/u/PetrKryslUCSD)
#### Post date: [March 9, 2023, 1:51am UTC](https://discourse.julialang.org/t/behavior-of-threads/95769/7 "2023-03-09T01:51:26Z")

</div>

Also relevant: [Behavior of `Threads.@threads for` loop](https://discourse.julialang.org/t/behavior-of-threads-threads-for-loop/76042)  
With solution by @carstenbauer

---

<div class="post-metadata">

### Author: ![carstenbauer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/carstenbauer/32/4981_2.png) [@carstenbauer](https://discourse.julialang.org/u/carstenbauer)
#### Post date: [March 9, 2023, 8:01am UTC](https://discourse.julialang.org/t/behavior-of-threads/95769/8 "2023-03-09T08:01:55Z")

</div>

Note that the solution in this other thread isn’t quite accurate anymore, because the default scheduler of `@threads` [has changed from `:static` to `:dynamic` in Julia 1.8](https://docs.julialang.org/en/v1.8/NEWS/#Multi-threading-changes).

It’s pretty simple: With default options, neither `@threads` nor `@spawn` creates (thread-)_sticky_ tasks. Hence, there are no guarantees whatsoever about the task-thread mapping. In particular, you must assume that tasks can migrate between threads at any point in time. If you want guarantees (i.e. _sticky_ tasks), use `@threads :static` or `@tspawnat` from e.g. ThreadPools.jl or ThreadPinning.jl.

Note that, at this time, tasks might not migrate much in practice. But they can and you should assume that they do if you want to write stable code.

---

<div class="post-metadata">

### Author: ![carstenbauer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/carstenbauer/32/4981_2.png) [@carstenbauer](https://discourse.julialang.org/u/carstenbauer)
#### Post date: [March 9, 2023, 8:13am UTC](https://discourse.julialang.org/t/behavior-of-threads/95769/9 "2023-03-09T08:13:29Z")

</div>

> [@PetrKryslUCSD](#):
>
> Good points, everyone. To make my concern concrete: Consider the function
> 
> ```julia
> function f(a)
> th = Base.Threads.threadid()
> ... some code
> a[th] = 1
> ... some code
> th = Base.Threads.threadid()
> return a[th] == 1 ? 5 * a[th] : 0
> end
> 
> ```

Quite generally, `threadid()` is a footgun. Why? Because, as I indicated in my previous post, the default macros create non-sticky tasks, so the return value of `threadid()` can vary over the course of executing a piece of code. Fundamentally, this is because Julia offers **task** -based multithreading. The central concept is a task, not a thread.

(In this context, `@threads` is somewhat of a misnomer IMO. I think @vchuravy has voiced the same at some point. Something like `@parallel` or `@multithreaded` would have probably been a better name.)

> [@PetrKryslUCSD](#):
>
> This function takes an argument which is supposed to be a thread-private  
> storage.

For the reasons mentioned above, unless you have a good reason (which you might or might not have), you should try to think in terms of **task** -local storage rather than **thread** -local storage. For this, things like [`Base.task_local_storage`](https://docs.julialang.org/en/v1/base/parallel/#Base.task_local_storage-Tuple%7BAny%7D) and [ChunkSplitters.jl](https://github.com/m3g/ChunkSplitters.jl) might be helpful.

---

<div class="post-metadata">

### Author: ![PetrKryslUCSD](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/petrkryslucsd/32/215825_2.png) [@PetrKryslUCSD](https://discourse.julialang.org/u/PetrKryslUCSD)
#### Post date: [March 9, 2023, 4:09pm UTC](https://discourse.julialang.org/t/behavior-of-threads/95769/10 "2023-03-09T16:09:15Z")

</div>

> [@carstenbauer](#):
>
> For the reasons mentioned above, unless you have a good reason (which you might or might not have), you should try to think in terms of **task** -local storage rather than **thread** -local storage. For this, things like [`Base.task_local_storage`](https://docs.julialang.org/en/v1/base/parallel/#Base.task_local_storage-Tuple%7BAny%7D) and [ChunkSplitters.jl](https://github.com/m3g/ChunkSplitters.jl) might be helpful.

Thank you very much for this insight!

---

<div class="post-metadata">

### Author: ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)
#### Post date: [March 9, 2023, 7:50pm UTC](https://discourse.julialang.org/t/behavior-of-threads/95769/11 "2023-03-09T19:50:28Z")

</div>

> [@carstenbauer](#):
>
> Quite generally, `threadid()` is a footgun. Why? Because, as I indicated in my previous post, the default macros create non-sticky tasks, so the return value of `threadid()` can vary over the course of executing a piece of code. Fundamentally, this is because Julia offers **task** -based multithreading. The central concept is a task, not a thread.

Maybe this is a dumb question, but would it make sense to have a `taskid()`?

---

<div class="post-metadata">

### Author: ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)
#### Post date: [March 9, 2023, 8:32pm UTC](https://discourse.julialang.org/t/behavior-of-threads/95769/12 "2023-03-09T20:32:59Z")

</div>

> [@uniment](#):
>
> would it make sense to have a `taskid()`?

It would have more or less the same potential pitfalls as the current `threadid()` (other than being presumably a constant per task); that is, making an array of a fixed size and indexing it with `taskid()` can have hidden dataraces if you accidentally (or on purpose) access an entry that isn’t “for” your task. Storing things that other tasks shouldn’t mess with in a task local storage, on the other hand, prevents this kind of data race semantically and forces explicit sharing of state across tasks, leading to a less error-prone architecture by design.

---

<div class="post-metadata">

### Author: ![PetrKryslUCSD](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/petrkryslucsd/32/215825_2.png) [@PetrKryslUCSD](https://discourse.julialang.org/u/PetrKryslUCSD)
#### Post date: [March 9, 2023, 9:52pm UTC](https://discourse.julialang.org/t/behavior-of-threads/95769/13 "2023-03-09T21:52:50Z")

</div>

What you say makes sense. But, I do not have the option of copying things into a task-local storage: they are too big. I have to work with a global array. They way I understand it, once I give a task some data to process, no other task will ever interfere: meaning when I ask for a task id, it will always be the same. Isn’t that right?

---

<div class="post-metadata">

### Author: ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)
#### Post date: [March 9, 2023, 10:32pm UTC](https://discourse.julialang.org/t/behavior-of-threads/95769/14 "2023-03-09T22:32:29Z")

</div>

> [@PetrKryslUCSD](#):
>
> But, I do not have the option of copying things into a task-local storage: they are too big.

I’m not sure I follow - as far as I’m aware, there is no size limit on things you can put into `task_local_storage`:

```julia
julia> task_local_storage(:test, rand(10_000_000));

julia> task_local_storage(:test2, rand(10_000_000));

julia> task_local_storage(:test3, rand(1_000_000_000));

```

> [@PetrKryslUCSD](#):
>
> They way I understand it, once I give a task some data to process, no other task will ever interfere: meaning when I ask for a task id, it will always be the same. Isn’t that right?

Only insofar that hypothetical task id may stay the same. However, there can be other tasks being spawned as part of your algorithm, either internally or explicitly, that increase the highest task id. So there’s no equivalent to `nthreads` to use for sizing that global array you’re referencing. Internally, if you’ve already given the task all data it needs to compute its result, what would it need its own task id for? That number is not necessarily in `1:nthreads()` after all.

---

<div class="post-metadata">

### Author: ![PetrKryslUCSD](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/petrkryslucsd/32/215825_2.png) [@PetrKryslUCSD](https://discourse.julialang.org/u/PetrKryslUCSD)
#### Post date: [March 9, 2023, 10:35pm UTC](https://discourse.julialang.org/t/behavior-of-threads/95769/15 "2023-03-09T22:35:45Z")

</div>

I do not want to unnecessarily duplicate the data. It consumes memory, and a large chunk of it.

Each task should work by accessing only a little bit of the array, not the whole thing. In order for it to stay within the range it is supposed to see, I can use the chunk number. If I spawn as many tasks as there are chunks, I can identify the chunk by the task. Does that make sense?

---

<div class="post-metadata">

### Author: ![simsurace](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/simsurace/32/30216_2.png) [@simsurace](https://discourse.julialang.org/u/simsurace)
#### Post date: [March 9, 2023, 11:01pm UTC](https://discourse.julialang.org/t/behavior-of-threads/95769/16 "2023-03-09T23:01:30Z")

</div>

That‘s exactly what ChunkSplitters.jl are doing, and threads don‘t need copies, they use shared memory.

---

<div class="post-metadata">

### Author: ![ffevotte](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ffevotte/32/6587_2.png) [@ffevotte](https://discourse.julialang.org/u/ffevotte)
#### Post date: [March 9, 2023, 11:04pm UTC](https://discourse.julialang.org/t/behavior-of-threads/95769/17 "2023-03-09T23:04:04Z")

</div>

> [@PetrKryslUCSD](#):
>
> Each task should work by accessing only a little bit of the array, not the whole thing. In order for it to stay within the range it is supposed to see, I can use the chunk number. If I spawn as many tasks as there are chunks, I can identify the chunk by the task. Does that make sense?

Yes, it makes sense. This is more or less the way [`ChunkSplitters`](https://github.com/m3g/ChunkSplitters.jl) works and, as far as I’m aware, this approach is safe even when tasks can migrate.

In the example given in the ChunkSplitters README, `Threads.@threads` will spawn as many tasks as there are chunks, and `ichunk` identifies a chunk (and maybe this is nitpicking, but `ichunk` may not really be considered as a task identifier, because there might exist many other tasks that were not spawned by this `Threads.@threads` invocation)

```julia
function sum_parallel(f, x; nchunks=Threads.nthreads())
    s = fill(zero(eltype(x)), nchunks)
    Threads.@threads for ichunk in 1:nchunks
        for i in chunks(x, ichunk, nchunks)
            s[ichunk] += f(x[i])
        end
    end
    return sum(s)
end

```

---

<div class="post-metadata">

### Author: ![j-fu](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/j-fu/32/11373_2.png) [@j-fu](https://discourse.julialang.org/u/j-fu)
#### Post date: [March 9, 2023, 11:05pm UTC](https://discourse.julialang.org/t/behavior-of-threads/95769/18 "2023-03-09T23:05:03Z")

</div>

I would second @PetrKryslUCSD : when working with large problems it appears to be prohibitive to copy all the data into task local storage. Instead, one would partition the index sets and take care about avoiding write conflicts by proper scheduling of non-conflicting accesses (e.g. by coloring the partition graph and having several multithreaded loops over each of the colors).

I am still puzzled by `threadid()` as a footgun. How and why is this different from `omp_get_thread_num()` ? My mental model for multitrheading comes from OpenMP (admittely also from a couple of years in the past).

I plan do dive deeper into this, but probably will find time only in a couple of weeks.

---

<div class="post-metadata">

### Author: ![PetrKryslUCSD](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/petrkryslucsd/32/215825_2.png) [@PetrKryslUCSD](https://discourse.julialang.org/u/PetrKryslUCSD)
#### Post date: [March 9, 2023, 11:08pm UTC](https://discourse.julialang.org/t/behavior-of-threads/95769/19 "2023-03-09T23:08:17Z")

</div>

I’m sorry if I am not making a crystal clear argument. ChunkSplitters is great, but I want to use a function which has been designed as serial, and it calls into another function (which is supposed work on a part of a global array). I don’t want to pass another argument to either one of them (i.e. the chunk number) when it is run in a parallel task. So somehow I need to propagate that chunk number through the task from the calling environment where I spawned the task to an interior function two layers down.

---

<div class="post-metadata">

### Author: ![simsurace](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/simsurace/32/30216_2.png) [@simsurace](https://discourse.julialang.org/u/simsurace)
#### Post date: [March 9, 2023, 11:31pm UTC](https://discourse.julialang.org/t/behavior-of-threads/95769/20 "2023-03-09T23:31:18Z")

</div>

Maybe I‘m not understanding the setting, but it seems that just using `Threads.@threads` on the loop calling the function on the individual parts of the array should work (without using `threadid()`).

[Next page](https://discourse.julialang.org/t/behavior-of-threads/95769.md?page=2)
