# Is \`@async\` memory safe?

**URL:** <https://discourse.julialang.org/t/is-async-memory-safe/66558>\
**Category:** General Usage\
**Tags:** multithreading\
**Created:** [August 17, 2021, 3:37pm UTC](https://discourse.julialang.org/t/is-async-memory-safe/66558 "2021-08-17T15:37:05Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![heliosdrm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/heliosdrm/32/3851_2.png) [@heliosdrm](https://discourse.julialang.org/u/heliosdrm)\
**Post date:** [August 17, 2021, 3:37pm UTC](https://discourse.julialang.org/t/is-async-memory-safe/66558/1 "2021-08-17T15:37:05Z")

</div>

The page of the manual on [multi-threading](https://docs.julialang.org/en/v1/manual/multi-threading/) warns about memory-unsafe operations in data race conditions, and gives the advice of using locks or atomic operations on `Threads.Atomic` objects to work around that problem.

I get that this applies e.g. when you `@spawn` different tasks that may write and read the same data in different threads. But does this also happen for “green threading” tasks, when scheduled with `@async`? The section on [asynchronous programming](https://docs.julialang.org/en/v1/manual/asynchronous-programming/) that goes right before multi-threading makes no mention of such problems, so my impression is that this problem is specific of “real” multi-threading, but I have the gut feeling that it’s not like that.

---

<div class="post-metadata">

**Author:** ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)\
**Post date:** [August 17, 2021, 3:37pm UTC](https://discourse.julialang.org/t/is-async-memory-safe/66558/2 "2021-08-17T15:37:57Z")

</div>

`@async` only has 1 thread running so it’s thread-safe by construction I believe.

---

<div class="post-metadata">

**Author:** ![heliosdrm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/heliosdrm/32/3851_2.png) [@heliosdrm](https://discourse.julialang.org/u/heliosdrm)\
**Post date:** [August 17, 2021, 3:40pm UTC](https://discourse.julialang.org/t/is-async-memory-safe/66558/3 "2021-08-17T15:40:22Z")

</div>

So, if you have multiple available threads, and schedule two tasks with `@async`, are they both guaranteed to run in the same thread? Which one? The one in which they were scheduled, maybe?

---

<div class="post-metadata">

**Author:** ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)\
**Post date:** [August 17, 2021, 3:42pm UTC](https://discourse.julialang.org/t/is-async-memory-safe/66558/4 "2021-08-17T15:42:08Z")

</div>

```julia
julia> a = Int[]
Int64[]

julia> @sync for _ in 1:10
           @async for _ in 1:10
               push!(a, Threads.threadid())
               sleep(0.01)
           end
           @async for _ in 1:10
               push!(a, Threads.threadid())
           end
       end

julia> all(==(1), a)
true

```

---

<div class="post-metadata">

**Author:** ![ericphanson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ericphanson/32/215186_2.png) [@ericphanson](https://discourse.julialang.org/u/ericphanson)\
**Post date:** [August 18, 2021, 1:00am UTC](https://discourse.julialang.org/t/is-async-memory-safe/66558/5 "2021-08-18T01:00:54Z")

</div>

Xref

> [@Task scheduling semantics](https://discourse.julialang.org/t/task-scheduling-semantics/62950/2):
>
> I needed to know about the new behavior but was also confused about where to look. To link up some information I found: The co-scheduling behavior of @async tasks with their parent tasks was discussed in [https://github.com/JuliaLang/julia/issues/41324](https://github.com/JuliaLang/julia/issues/41324) For 1.7, it was decided that unsticky tasks become sticky if they launch any children with @async, thus ensuring that @async tasks always run on the same thread as their parent. [https://github.com/JuliaLang/julia/pull/41334](https://github.com/JuliaLang/julia/pull/41334) This probably isn’t…

---

<div class="post-metadata">

**Author:** ![c42f](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/c42f/32/52842_2.png) [@c42f](https://discourse.julialang.org/u/c42f)\
**Post date:** [August 18, 2021, 6:45am UTC](https://discourse.julialang.org/t/is-async-memory-safe/66558/6 "2021-08-18T06:45:24Z")

</div>

> [@jling](#):
>
> `@async` only has 1 thread running so it’s thread-safe by construction I believe

Yes, this is correct if you’re worried about _data races_ which are only a concern for true parallelism.

You may, however still need locks if there’s any chance that a `Task` (green thread) might yield to another task which manipulates the same data. In Julia it’s not easy to audit possible yield points, so I’d generally suggest locking around any shared data structures which aren’t explicitly documented as threadsafe. (This will also help you later, if you decide to use `Threads.@spawn` rather than `@async`.)

---

<div class="post-metadata">

**Author:** ![tisztamo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tisztamo/32/16200_2.png) [@tisztamo](https://discourse.julialang.org/u/tisztamo)\
**Post date:** [August 18, 2021, 6:50am UTC](https://discourse.julialang.org/t/is-async-memory-safe/66558/7 "2021-08-18T06:50:52Z")

</div>

> [@c42f](#):
>
> You may, however still need locks if there’s any chance that a `Task` (green thread) might yield to another task which manipulates the same data.

I cannot grasp this, could you please elaborate a bit? (e.g. What counts as “maniupulating the same data”?)

---

<div class="post-metadata">

**Author:** ![tkf](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tkf/32/17635_2.png) [@tkf](https://discourse.julialang.org/u/tkf)\
**Post date:** [August 18, 2021, 7:48am UTC](https://discourse.julialang.org/t/is-async-memory-safe/66558/8 "2021-08-18T07:48:10Z")

</div>

Consider the following program:

```julia
using StructArrays

function f(a, b)
    v = StructVector((a, b))
    @sync begin
        @async push!(v, (1, 2))

        x = @inbounds v[end]
        @show x
    end
end

```

Is this program well-defined for `f(::Vector, ::Vector)`? Likely yes. (But there is no public API guaranteeing this is definitely the case.)

Is this program well-defined for `f(::AbstractVector, ::AbstractVector)`? No. This is because `push!(::AbstractVector, 1)` may contain an I/O; e.g., reading a file, accessing internet, or even just a `@debug` statement. In such case, the scheduler may decide to switch to the parent task while executing `push!(a, 1)` or `push!(b, 2)` in the child task. Now, in the parent task, the invariance of `StructVector` (e.g., the underlying arrays have consistent size) can completely be violated and no API can be expected to work.

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.)

[1] In contrast, many other languages such as Python, JavaScript, C++, Rust, … that added coroutines after the language is designed tend to have `async`/`awit` keywords which clarify when the coroutine may yield to the scheduler. They have [another issue with it though](https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/).

---

<div class="post-metadata">

**Author:** ![tisztamo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tisztamo/32/16200_2.png) [@tisztamo](https://discourse.julialang.org/u/tisztamo)\
**Post date:** [August 18, 2021, 10:29am UTC](https://discourse.julialang.org/t/is-async-memory-safe/66558/9 "2021-08-18T10:29:23Z")

</div>

Thanks for this great explanation!

This seems like a source of rare but hard to find bugs. A quick mental search in my recent programs did not found any possible instance of this, but it seems entirely possible, especially with `@debug`. Shared memory is evil.

---

<div class="post-metadata">

**Author:** ![c42f](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/c42f/32/52842_2.png) [@c42f](https://discourse.julialang.org/u/c42f)\
**Post date:** [August 18, 2021, 1:11pm UTC](https://discourse.julialang.org/t/is-async-memory-safe/66558/10 "2021-08-18T13:11:03Z")

</div>

> [@tkf](#):
>
> 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

This is a nice picture to have in mind for `@spawn`, though to connect to the original question about data races — you need the program to be free of data races in order to interpret the execution as a sequentially consistent “interleaving”.

---

<div class="post-metadata">

**Author:** ![tkf](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tkf/32/17635_2.png) [@tkf](https://discourse.julialang.org/u/tkf)\
**Post date:** [August 18, 2021, 10:43pm UTC](https://discourse.julialang.org/t/is-async-memory-safe/66558/11 "2021-08-18T22:43:29Z")

</div>

> [@tisztamo](#):
>
> Shared memory is evil.

We shake hands with the devil, and get the performance 😈

> [@c42f](#):
>
> you need the program to be free of data races in order to interpret the execution as a sequentially consistent “interleaving”

Yeah, good point. I avoided mentioning memory ordering since I realized my comment was getting long. But I agree that it’s worth mentioning that using `@spawn` is hard since even mere interleaving is not automatic.

---

<div class="post-metadata">

**Author:** ![dlakelan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dlakelan/32/8491_2.png) [@dlakelan](https://discourse.julialang.org/u/dlakelan)\
**Post date:** [August 19, 2021, 2:31am UTC](https://discourse.julialang.org/t/is-async-memory-safe/66558/12 "2021-08-19T02:31:55Z")

</div>

> [@tisztamo](#):
>
> Shared memory is evil.

As someone who was a BeOS developer, I strongly disagree. The important thing is to understand what you are doing. Message passing through channels and using synchronization primitives is really not that hard. In many ways multithreaded programming is easier, it just isn’t like what people are used to. A good threading tutorial would be helpful I think.

---

<div class="post-metadata">

**Author:** ![c42f](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/c42f/32/52842_2.png) [@c42f](https://discourse.julialang.org/u/c42f)\
**Post date:** [August 19, 2021, 6:26am UTC](https://discourse.julialang.org/t/is-async-memory-safe/66558/13 "2021-08-19T06:26:21Z")

</div>

> [@tkf](#):
>
> We shake hands with the devil, and get the performance 😈

Haha! There’s even some algorithms which intentionally accept data races when some randomness is acceptable. It seems to work well in machine learning with `Hogwild!` and `Hogbatching` - [https://arxiv.org/pdf/1106.5730.pdf](https://arxiv.org/pdf/1106.5730.pdf) [https://people.ece.ubc.ca/matei/papers/ipdps16.pdf](https://people.ece.ubc.ca/matei/papers/ipdps16.pdf)

---

<div class="post-metadata">

**Author:** ![tkf](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tkf/32/17635_2.png) [@tkf](https://discourse.julialang.org/u/tkf)\
**Post date:** [August 19, 2021, 7:14am UTC](https://discourse.julialang.org/t/is-async-memory-safe/66558/14 "2021-08-19T07:14:33Z")

</div>

Yeah, a similar idea is called _chaotic relaxation_ and apparently, you can [track back the idea to 60s (!)](https://doi.org/10.1016/0024-3795(69)90028-7). Though the C++ committee says, even with the C++20 memory model:

> In theory, these algorithms are subject to OOTA and RFUB behaviors, however, in  
> practice, current implementations avoid such behaviors.
> 
> * * *
> 
> P2055R0: A Relaxed Guide to `memory_order_relaxed`  
> [http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2020/p2055r0.pdf](http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2020/p2055r0.pdf)

OOTA: out-of-thin-air; RFUB: read-from-untaken-branch
