# @async macro not recommended?

**URL:** <https://discourse.julialang.org/t/async-macro-not-recommended/112404>\
**Category:** General Usage\
**Created:** [April 2, 2024, 8:33am UTC](https://discourse.julialang.org/t/async-macro-not-recommended/112404 "2024-04-02T08:33:33Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![Tarny\_GG\_Channie](https://avatars.discourse-cdn.com/v4/letter/t/3bc359/32.png) [@Tarny\_GG\_Channie](https://discourse.julialang.org/u/Tarny_GG_Channie)\
**Post date:** [April 2, 2024, 8:33am UTC](https://discourse.julialang.org/t/async-macro-not-recommended/112404/1 "2024-04-02T08:33:34Z")

</div>

According to the documentation:

> It is strongly encouraged to favor `Threads.@spawn` over `@async` always **even when no parallelism is required** especially in publicly distributed libraries. This is because a use of `@async` disables the migration of the _parent_ task across worker threads in the current implementation of Julia. Thus, seemingly innocent use of `@async` in a library function can have a large impact on the performance of very different parts of user applications.

What async does is simply scheduling the thread in a local machine.  
What’s happening?

Is this going to be fixed in the future?

When to use async vs spawn?

My mental model was that async is similar to a green thread whereas spawn actually spawns another thread but what actually happened?

---

<div class="post-metadata">

**Author:** ![abraemer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/abraemer/32/51403_2.png) [@abraemer](https://discourse.julialang.org/u/abraemer)\
**Post date:** [April 2, 2024, 8:54am UTC](https://discourse.julialang.org/t/async-macro-not-recommended/112404/2 "2024-04-02T08:54:16Z")

</div>

`@async` and `@spawn` actually do almost the very same thing: They wrap the contained code in a `Task` and `schedule` it. The only difference is in the scheduling. `@async` sets the `sticky` flag, which means the `Task` is not allowed to migrate to another thread and this then extends to the parent `Task` that spawned (and its parent and so on). This is generally unwanted because it affects load-balancing. Julia is actually one of very few languages where threading composes well due to its dynamic, depth-first scheduler but that requires `Task`s to be able to change their worker threads. Read more about it here:

> **[Announcing composable multi-threaded parallelism in Julia](https://julialang.org/blog/2019/07/multithreading/)**
>
> Announcing composable multi-threaded parallelism in Julia | Software performance depends more and more on exploiting multiple processor cores....

To summarize: The core problem is that `@async` can cause tremendous slowdowns in unrelated code parts because it disables migration of `Tasks` which is needed for efficient, composable multi-threading.

> [@Tarny\_GG\_Channie](#):
>
> My mental model was that async is similar to a green thread whereas spawn actually spawns another thread but what actually happened?

This model is wrong. Julia never adds threads dynamically. There is a fixed number of worker threads that switch between all scheduled `Task`s. You could view the creation of a `Task` as similar to spawning a green thread.

---

<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:** [April 2, 2024, 10:00pm UTC](https://discourse.julialang.org/t/async-macro-not-recommended/112404/3 "2024-04-02T22:00:23Z")

</div>

Too add some more nuance here.

The number of threads in Julia can change, but currently Julia doesn’t dynamically add threads, the only way for adding additional threads is for so called “foreign thread adoption”. E.g. a C/C++ library using it’s own threads underneath and ending up calling back into Julia. That thread will get adopted into Julia and so you may have `Base.Threads.nthreads()` being different than `Base.Threads.maxthreadid()`.

---

<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:** [April 3, 2024, 5:08am UTC](https://discourse.julialang.org/t/async-macro-not-recommended/112404/4 "2024-04-03T05:08:20Z")

</div>

> [@vchuravy](#):
>
> Julia doesn’t dynamically add tasks, the only way for adding additional tasks

I think you wanted to say “threads” here.

---

<div class="post-metadata">

**Author:** ![phantom](https://avatars.discourse-cdn.com/v4/letter/p/e0b2c6/32.png) [@phantom](https://discourse.julialang.org/u/phantom)\
**Post date:** [April 3, 2024, 5:39am UTC](https://discourse.julialang.org/t/async-macro-not-recommended/112404/5 "2024-04-03T05:39:33Z")

</div>

So are there cases where `@async` would still be preferred over `@spawn` in order to avoid race conditions? E.g. if you have a parallel mutations of arrays that aren’t thread safe as noted by stevengj [here](https://discourse.julialang.org/t/best-data-structure-for-recursive-functions/112289). Sorry i’m still a little confused about this point.

---

<div class="post-metadata">

**Author:** ![abraemer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/abraemer/32/51403_2.png) [@abraemer](https://discourse.julialang.org/u/abraemer)\
**Post date:** [April 3, 2024, 5:57am UTC](https://discourse.julialang.org/t/async-macro-not-recommended/112404/6 "2024-04-03T05:57:30Z")

</div>

Not really no as you have no guarantees where a Task pauses (AFAIK). It might be that in practice you will avoid some race conditions (e.g. if the compiler never inserts a yield into something like `array[i] += value` making it _practically_ an atomic operation). But I’d never want to rely on this! Julia has no way of controlling the yield points so you always had to guess where they are and hope - which is not how I would want to construct my programs 😅

To quote a [previous thread](https://discourse.julialang.org/t/is-async-memory-safe/66558/8):

> [@Is \`@async\` memory safe?](https://discourse.julialang.org/t/is-async-memory-safe/66558/8):
>
> The semantics of `@async` and `@spawn` tasks allow arbitrary interleaving of “pieces of code.” The only difference is that the granularity of the “pieces of code”. In `@async`, it’s the parts of the code delimited by the yield points (“I/O” operations). In `@spawn`, it’s individual machine instructions. So, this is why people tend to worry about the correctness of concurrent programs only when `@spawn` is used. More interleavings are possible and so it increases the possibility that bad things happen. However, since there is nothing in Julia language that makes sure certain function/method call does not contain a yield point [1], it is not correct to assume that manipulating a data structure from multiple `@async` tasks is safe, unless you read all the code reachable from the functions you are calling. (I guess it sounds rather like a FUD but I feel it is necessary for clarifying the current situation of concurrent programming in Julia.)
