How to properly do thread-safe element-wise mutation on atomic fields?

MWE:

julia> mutable struct Atomic{T}
           @atomic a::T
       end

julia> obj1 = Atomic(collect(1:4))
Atomic{Vector{Int64}}([1, 2, 3, 4])

julia> obj1.a .= 2
4-element Vector{Int64}:
 2
 2
 2
 2

Unlike directly changing the field itself (@atomic obj1.a = [2,2,2,2]), it seems that this operation does not require the macro annotation. I wonder if doing this is also thread-safe? I would guess it’s not, since obj1.a itself is just a Vector referenced by obj1. As we perform element-wise mutation, we are no longer modifying obj1, but rather interacting directly with obj1.a.

If that’s the case, is there a way to automatically make a container-type (e.g., Array) object thread-safe using atomic formalism? Or is the only way to do it just to apply a lock? For instance:

function safely_set!(box::AbstractArray{T, N}, val::T) where {T, N}
    lk = ReentrantLock()
    lock(lk) do
        box .= val
    end
    box
end

Thanks!!

Julia 1.11 added Memory (and specifically AtomicMemory which allows you to do this.

1 Like

Thanks for pointing in the right direction! I checked the official documentation, and it seems that the higher-level API of AtomicMemory hasn’t been finalized. Using methods, I figured out one way to construct an instance of it:

julia> v = AtomicMemory{Int}(undef, 2)
2-element AtomicMemory{Int64}:
 3172034123184
 3172034123216

However, I still don’t know how I can modify its entries. The existing documentation on Core.memoryrefset! does not provide a method with an argument for the memory index. May I ask if the typical interface methods, such as setindex!, will be included in the upcoming 1.12?

Thanks!!

If you use the dev versions of the docs, you’ll get more useful results. Specifically, you can just do

v = AtomicMemory{Int}(undef, 2)
@atomic :aquire v[1] = 2

etc.

1 Like

Thanks!! I’ll check them out!