# Examples of when to use @async over @spawn?

**URL:** <https://discourse.julialang.org/t/examples-of-when-to-use-async-over-spawn/114631>\
**Category:** Performance\
**Tags:** question\
**Created:** [May 23, 2024, 3:36pm UTC](https://discourse.julialang.org/t/examples-of-when-to-use-async-over-spawn/114631 "2024-05-23T15:36:38Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![Satvik](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/satvik/32/20486_2.png) [@Satvik](https://discourse.julialang.org/u/Satvik)\
**Post date:** [May 23, 2024, 3:36pm UTC](https://discourse.julialang.org/t/examples-of-when-to-use-async-over-spawn/114631/1 "2024-05-23T15:36:38Z")

</div>

I’m trying to figure out if there’s ever a good reason to use `@async` over `Threads.@spawn` anymore.

It seems like you want `Threads.@spawn` the vast majority of the time – it’s more composable, gives more freedom to the scheduler, and is usually higher performance.

In some languages, the recommendation is to use the equivalent of `@async` most of the time to avoid parallelization overhead. But the overhead of `Threads.@spawn` is quite small, to the point where you’ll generally see performance gains unless your tasks are \<1ms.

I suppose if you had a case with really small tasks that needed to be concurrent, `@async` might be more efficient, but I’m struggling to come up with an example.

---

<div class="post-metadata">

**Author:** ![vchuravy](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/vchuravy/32/8_2.png) [@vchuravy](https://discourse.julialang.org/u/vchuravy)\
**Post date:** [May 23, 2024, 4:36pm UTC](https://discourse.julialang.org/t/examples-of-when-to-use-async-over-spawn/114631/2 "2024-05-23T16:36:06Z")

</div>

`@async` and `@spawn` have the same cost. The difference is in the semantics and for new code there is no reason to use `@async`. We needed `@spawn` to not break existing code that depended on the precise semantics of `@async`

---

<div class="post-metadata">

**Author:** ![jbytecode](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jbytecode/32/17719_2.png) [@jbytecode](https://discourse.julialang.org/u/jbytecode)\
**Post date:** [May 23, 2024, 4:39pm UTC](https://discourse.julialang.org/t/examples-of-when-to-use-async-over-spawn/114631/3 "2024-05-23T16:39:15Z")

</div>

Perhaps, does it help?

> [@How does Julia Async Task Scheduler work?](https://discourse.julialang.org/t/how-does-julia-async-task-scheduler-work/96675/4):
>
> It’s also worth noting that there are two different ways to spawn new tasks: @async which will always run the task on the same hardware thread as the current task, and will only make progress when the current task blocks and yields back to the scheduler; @spawn which creates a task that can be run on any hardware thread, so it can make progress while the current task is still working if there are other threads available. You can synchronize both with @sync.

---

<div class="post-metadata">

**Author:** ![Satvik](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/satvik/32/20486_2.png) [@Satvik](https://discourse.julialang.org/u/Satvik)\
**Post date:** [May 24, 2024, 5:53pm UTC](https://discourse.julialang.org/t/examples-of-when-to-use-async-over-spawn/114631/4 "2024-05-24T17:53:25Z")

</div>

Thanks! Would you also say `Threads.@threads` is obsolete and we should use `Threads.@spawn` for new code?

---

<div class="post-metadata">

**Author:** ![Jeff\_Emanuel](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jeff_emanuel/32/15440_2.png) [@Jeff\_Emanuel](https://discourse.julialang.org/u/Jeff_Emanuel)\
**Post date:** [May 24, 2024, 6:14pm UTC](https://discourse.julialang.org/t/examples-of-when-to-use-async-over-spawn/114631/5 "2024-05-24T18:14:25Z")

</div>

[@threads](https://docs.julialang.org/en/v1/base/multi-threading/#Base.Threads.@threads) applies to for loops. [@spawn](https://docs.julialang.org/en/v1/base/multi-threading/#Base.Threads.@spawn) applies to arbitrary code.

---

<div class="post-metadata">

**Author:** ![Satvik](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/satvik/32/20486_2.png) [@Satvik](https://discourse.julialang.org/u/Satvik)\
**Post date:** [May 24, 2024, 6:28pm UTC](https://discourse.julialang.org/t/examples-of-when-to-use-async-over-spawn/114631/6 "2024-05-24T18:28:49Z")

</div>

`@threads` seems to have pretty different performance characteristics though:

```julia
n = Threads.nthreads()

function hash_lots(x)
    for i in 1:10_000_000
        x = hash(x)
    end
    sleep(.075)
    return x
end

@btime hash_lots(5)
> 151.253 ms

@btime Threads.@threads for i in 1:(4*n)
      hash_lots(i)
end
> 608.548 ms (606 allocations: 53.75 KiB)

@btime begin 
    @sync for i in 1:(4*n)
        Threads.@spawn hash_lots(i)
    end
end
> 381.235 ms (974 allocations: 85.27 KiB)

```

It looks like `@threads` doesn’t allow task switching, while `@spawn` does. So I’m wondering if it ever makes sense to use `@threads` over `@spawn` even when you have a for loop.

---

<div class="post-metadata">

**Author:** ![vchuravy](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/vchuravy/32/8_2.png) [@vchuravy](https://discourse.julialang.org/u/vchuravy)\
**Post date:** [May 27, 2024, 4:28pm UTC](https://discourse.julialang.org/t/examples-of-when-to-use-async-over-spawn/114631/7 "2024-05-27T16:28:00Z")

</div>

In the `@spawn` case you create `4*n` tasks and in the `@threads` case, we internally create `threadpoolsize()` tasks. `@threads` processes work in batches.

So due to the `sleep` in hash\_lots with many tasks we can overlap the execution, whereas `@threads` is can’t.

```julia
julia> @btime Threads.@threads for i in 1:(4*n)
             sleep(.075)
       end
  305.822 ms (342 allocations: 16.12 KiB)

julia> @btime begin
           @sync for i in 1:(4*n)
               Threads.@spawn sleep(.075)
           end
       end
  76.265 ms (588 allocations: 41.23 KiB)

julia> @btime begin
           @sync for i in 1:n
               Threads.@spawn for _ in 1:4; sleep(.075); end
           end
       end
  306.316 ms (349 allocations: 16.64 KiB)

```

---

<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:** [May 27, 2024, 5:36pm UTC](https://discourse.julialang.org/t/examples-of-when-to-use-async-over-spawn/114631/8 "2024-05-27T17:36:00Z")

</div>

> [@vchuravy](#):
>
> `@threads` processes work in batches.

Note that this is only the case with the default scheduler. In 1.11, there will be the [`:greedy` scheduler](https://github.com/JuliaLang/julia/pull/52096), which eagerly consumes pieces of work and doesn’t batch. It spawns up to `threadpoolsize()` tasks that iterate independently. The intended use is for non-uniform workloads.

---

<div class="post-metadata">

**Author:** ![vchuravy](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/vchuravy/32/8_2.png) [@vchuravy](https://discourse.julialang.org/u/vchuravy)\
**Post date:** [May 28, 2024, 2:26am UTC](https://discourse.julialang.org/t/examples-of-when-to-use-async-over-spawn/114631/9 "2024-05-28T02:26:17Z")

</div>

Even with the :greedy scheduler you would see the same “issue” in this code as with batching.

The available concurrency is `threadpoolsize` not `n`. So work that could be interleaved isn’t necessarily
