Freethreading += operators (and similar)

Just considering the builtin numeric types here (so int, float, complex).

In the freethreading build, suppose I have two threads both doing in-place mathematical operations. What is guaranteed? Are the operations “atomic” or is a += 1 split into:

  1. read a
  2. add 1 to a
  3. replace a with the result.
    with the possibility that another thread could also write to a in the space between 2 and 3.

(I’m assuming that the one thing that is guaranteed is that the reference counting is fine, whatever else happens).

Are you just curious or are you having a specific problem you are addressing? Your answer may help us help you.

I’m asking from the point of view of implementing it in Cython (which currently isn’t very thread-safe, will have to get more thread-safe to support the free-threading build well, but I want to understand how thread-safe it ideally needs to be)

So I’m interested in “what does the interpreter guarantee?” rather than “should I write code with a bunch in place arithmetic operators, many threads, and no synchronisation?”. I know the answer to the second question is “no” but I also know people will do it anyway.

If using free threading, I believe you would have to use threading.Lock or something similar to guarantee deterministic behavior.

I don’t think things like += are guaranteed to be atomic.

Though it makes me think a stdlib of atomic numerics might be useful for some.

That fits with how I’d expect it to be implemented. And is probably easiest for me too so I’m happy if that’s true.

I don’t have a view on standard library atomics at this point. I guess it’s the usual argument of “could this be a third party pip package first”

As I understand things, it’s sort of an open question right now. Some operations in CPython are atomic, some things aren’t, and some things are “atomic-by-implementation”, meaning that the current interpreter (with the GIL) would never switch threads in the middle of them, but there is no formal guarantee. I wouldn’t be surprised if += falls into that category at the moment.

Until the freethreading build there wasn’t an strong need to formalize the list of atomic operations, but at this point it would be nice to know what was defined as atomic in the language[1] versus what happens to be atomic for now.

  1. which would be relevant to other Python implementations ↩︎

I’d think the same questions might actually apply to the regular Python interpreter too, in that if += is broken into multiple steps then the GIL could be released between steps. So possibly that’s just “atomic-by-a-different-implementation” (or not).

Yeah that’s what I was getting at, albeit from the other side–if it’s not atomic now it certainly wouldn’t be in the freethreading version, unless there’s a change to what is guaranteed.

This page in the docs suggests a list of atomic operations but I don’t think it’s a language guarantee and the page itself is pretty old (the next section definitely needs an update :wink: )

1 Like

Not if you switch the page to the 3.13… (I came across that next section and updated it myself a few month ago)

1 Like