# Spawning threads boxes variable - spooky action at a distance?

**URL:** <https://discourse.julialang.org/t/spawning-threads-boxes-variable-spooky-action-at-a-distance/97972>\
**Category:** General Usage\
**Created:** [April 26, 2023, 9:45pm UTC](https://discourse.julialang.org/t/spawning-threads-boxes-variable-spooky-action-at-a-distance/97972 "2023-04-26T21:45:23Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![anicusan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/anicusan/32/30094_2.png) [@anicusan](https://discourse.julialang.org/u/anicusan)\
**Post date:** [April 26, 2023, 9:45pm UTC](https://discourse.julialang.org/t/spawning-threads-boxes-variable-spooky-action-at-a-distance/97972/1 "2023-04-26T21:45:23Z")

</div>

Hi,

While multi-threading some code, I came across an odd case where spawning tasks leads to Julia boxing a variable _in a different code section_ (leading to type instabilities, `Any`, bad performance, the whole shebang).

I managed to reduce it to this contrived MWE:

```julia
function kernel!(clusters, points, irange)
    # Pseudo-cluster assignment
    for i in irange[1]:irange[2]
        clusters[i] = rand(1:length(points))
    end
end

function mtbox(points, num_tasks)

    num_points = size(points, 2)
    clusters = similar(points, Int64, num_points)
    prev_clusters = similar(points, Int64, num_points)

    # Keep track of tasks spawned
    tasks = Vector{Task}(undef, num_tasks)

    for it in 1:50

        # Swap current and previous iteration's cluster assignments
        clusters, prev_clusters = prev_clusters, clusters

        for itask in 1:num_tasks
            # Compute element indices handled by this task
            per_task = (num_points + num_tasks - 1) ÷ num_tasks
            task_istart = (itask - 1) * per_task + 1
            task_istop = min(itask * per_task, num_points)

            # Launch task over computed index range
            tasks[itask] = Threads.@spawn kernel!(
                clusters,
                points,
                (task_istart, task_istop),
            )
        end

        for task in tasks
            wait(task)
        end
    end

    clusters
end

# Example usage
mtbox(rand(3, 10), 4)

```

Using `Cthulhu.@descend mtbox(rand(3, 10), 4)`, we immediately see the problem (attached as a screenshot to highlight colours):

 ![Screenshot_20230426_223256](https://global.discourse-cdn.com/julialang/original/3X/4/5/452596e4e3123a9a64e53eb7920c3e140e70ea2f.png)

We get a `clusters::Core.Box` which leads to `clusters, prev_clusters::Any = prev_clusters, clusters::Any`.

However, if we remove the `clusters, prev_clusters = prev_clusters, clusters` line, everything becomes type-stable again:

 ![Screenshot_20230426_223719](https://global.discourse-cdn.com/julialang/original/3X/c/e/ce4945da4bdace8387fcc27e1e8e832068fcd8fe.png)

In isolation, neither of these lead to type instabilities:

- Swapping variable names with `clusters, prev_clusters = prev_clusters, clusters`.
- Launching tasks using `clusters` as a function argument.

But together, we get this sort of spooky action at a distance where the types cannot be inferred in one part of the code due to another.

I do not have much experience with type inference - would someone know why this happens, and perhaps if there are any solutions to it?

Thank you,  
Leonard

---

<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:** [April 28, 2023, 7:07am UTC](https://discourse.julialang.org/t/spawning-threads-boxes-variable-spooky-action-at-a-distance/97972/2 "2023-04-28T07:07:46Z")

</div>

> [@anicusan](#):
>
> would someone know why this happens, and perhaps if there are any solutions to it?

Here is my interpretation: `Threads.@spawn` will transform the code block you give it into an anonymous function, which is then scheduled for execution in a new `Task`. A side-effect of this is that variable `clusters` is captured inside the created closure, which causes the known performance gotcha described here:

[https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-captured](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-captured)

In the case of your MWE, `clusters` is never reassigned while a task is running (`kernel!` mutates it in each task, and the swap reassigns it in a purely sequential part of the code, when no parallel task is running). So if I’m not mistaken it should be correct to use the `let`-block trick described in the Performance Tips above, which would fix inference:

 ![shot-2023-04-28_09-03-23](https://global.discourse-cdn.com/julialang/original/3X/f/8/f81f49ff2fd774bc4133a3c4652cee8107001808.jpeg)

However, you’ll have to check whether the same kind of conditions hold in your real code, otherwise you might have hard to debug issues.

---

<div class="post-metadata">

**Author:** ![anicusan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/anicusan/32/30094_2.png) [@anicusan](https://discourse.julialang.org/u/anicusan)\
**Post date:** [May 16, 2023, 11:12am UTC](https://discourse.julialang.org/t/spawning-threads-boxes-variable-spooky-action-at-a-distance/97972/3 "2023-05-16T11:12:54Z")

</div>

That solves the problem, thank you! And thanks for the link, it makes sense that the variable is conservatively boxed in case it is mutated outside the running thread; in my case it is not, so the let-block works perfectly.
