1

I have an immutable object (a float) that is "written" by one thread and read by another one. Do I need synchronization for that?

class Foo:
   def __init(self):
      self._bar = None

   def writeAttribute(self, newValue):
      self._bar = newValue

   def readAttribute(self):
      return self._bar

Note that writeAttribute and readAttribute are called from different threads for the same instance. My understanding is that readAttribute returns a "reference" either to the old value or the new one, but nothing in between.

SebDieBln
  • 3,303
  • 1
  • 7
  • 21
  • Does this answer your question? [Are Python instance variables thread-safe?](https://stackoverflow.com/questions/8309902/are-python-instance-variables-thread-safe) – skowalak Mar 21 '22 at 18:49

1 Answers1

3

In CPython (the one from python.org) the Global Interpreter Lock ("GIL") ensures that only one thread at a time is executing Python bytecodes. Also, threads can be preempted in between bytecodes, but not "inside" them. That means bytecodes are atomic.

That means that every action that takes a single bytecode is thread-safe as a side-effect of the GIL.

Consider the following code, were the dis module is used to see bytecodes for a function:


In [2]: bar = 3.14
Out[2]: 3.14

In [3]: def modify(new):
   ...:     global bar
   ...:     bar = new
   ...:     

In [4]: dis.dis(modify)
  3           0 LOAD_FAST                0 (new)
              2 STORE_GLOBAL             0 (bar)
              4 LOAD_CONST               0 (None)
              6 RETURN_VALUE

Assigning a new value to the global bar takes only a single STORE_GLOBAL bytecode. So that should be thread-safe, in the sense that bar always points to an object. Its value is never undetermined.

Of note; it doesn't matter that a float object is immutable. The reference to that object (bar in the example) is mutable. It is that reference that you want to keep defined and unambiguous.

Second, instead of a getter/setter method in your class Foo I would propose that you use @property decorator. It is considered more Pythonic.

Edit

See also the FAQ: What kinds of global value mutation are thread-safe?

Edit2

Of course: when in doubt, use e.g. a Lock.

Roland Smith
  • 42,427
  • 3
  • 64
  • 94
  • Thanks for pointing out that the immutability of the object does not matter here, only the validity of the reference. – SebDieBln Mar 21 '22 at 19:53
  • Hmm, I don't know about global variables, but for their case, I don't think it's quite so safe. If you do `dis.dis(Foo().writeAttribute)` (which I think would be better anyway, instead of making up something different), you'll likely see a single `STORE_ATTR`, which I think *isn't* safe (if they write a `__setattr__` method they could execute arbitrary further Python code during its execution). – Kelly Bundy Mar 21 '22 at 19:53
  • @KellyBundy the FAQ explicitly states that: *Each bytecode instruction and therefore all the C implementation code reached from each instruction is therefore atomic from the point of view of a Python program.* So I guess that a `__settatr__` would not be a single `STORE_ATTR` bytecode. – Roland Smith Mar 21 '22 at 20:09
  • Not sure what you mean with `__settatr__ ` not being a single bytecode. It's not a bytecode at all. – Kelly Bundy Mar 21 '22 at 20:12
  • @KellyBundy What I mean is that a setter method would indeed have multiple bytecodes. But the *actual assignment* could still be a single bytecode. – Roland Smith Mar 21 '22 at 20:17
  • Right, but their threads aren't executing the *actual assignment*, they're calling the `writeAttribute` method. – Kelly Bundy Mar 21 '22 at 20:19
  • Yes, but as long as the *actual assignment* is atomic, then at no point `self._bar` would de undetermined. – Roland Smith Mar 21 '22 at 20:24