3

According to the Python Documentation, the append operation on Python list is atomic. At the same time addition operation is not atomic:

i = i + 1

I understand that the Python GIL is enforcing the append operation to be atomic. My question is why the GIL is not enforcing the same on addition operation?

Consider the following code:

In [24]: L = []

In [25]: def func1():
    ...:     L.append(2)
    ...:

In [26]: i = 0

In [27]: def func2():
    ...:     i = i + 2
    ...:

The bytecode of both functions are shown below:

In [28]: dis.dis(func1)
  2           0 LOAD_GLOBAL              0 (L)
              2 LOAD_METHOD              1 (append)
              4 LOAD_CONST               1 (2)
              6 CALL_METHOD              1
              8 POP_TOP
             10 LOAD_CONST               0 (None)
             12 RETURN_VALUE

In [29]: dis.dis(func2)
  2           0 LOAD_FAST                0 (i)
              2 LOAD_CONST               1 (2)
              4 BINARY_ADD
              6 STORE_FAST               0 (i)
              8 LOAD_CONST               0 (None)
             10 RETURN_VALUE

Python ensures that thread switch happens only between bytecode instructions. What is the difference in the bytecode instructions that make append operation atomic?

nitin_cherian
  • 6,405
  • 21
  • 76
  • 127
  • Addition itself is atomic (it's just a binary add) . But i = i + 2 is fundamentally two instructions: evaluate the right hand side, (i+1) , and *then* set the variable left hand side (i) equal to that result. – SimonR May 18 '20 at 16:16
  • Agree. i = i + 2 spans multiple bytecode instructions and so it is not atomic. But for me it looks like append operation also spans multiple bytecode instructions. Then how is append operation atomic? – nitin_cherian May 18 '20 at 16:20
  • 2
    @SimonR, Re, "Addition itself is atomic." Normally when we say, "X is atomic," We are talking about how the operation is seen by other threads, and since threads communicate through shared memory, we are talking about how the operation affects shared memory locations. Addition is _not_ inherently atomic because in order to perform "addition" on shared variables, the program must fetch one or two operands from memory and then write the result to memory. – Solomon Slow May 18 '20 at 16:58

1 Answers1

0

Because the append operation side-effects the list being appended to, and if it were not atomic it would have have no chance of having any semantics (and a good chance of dumping core). Addition, by contrast, is a much simpler process, and also is commutative, so doing i+=1 in one thread and i-=1 in another will give the same final result when the smoke clears.

Igor Rivin
  • 4,632
  • 2
  • 23
  • 35
  • Re, "doing i+=1 in one thread and i-=1 in another will give the same final result when the smoke clears." I don't know if that's true in Python, but it is _not_ true in most multi-threaded environments. – Solomon Slow May 18 '20 at 16:56
  • @Solomon Slow - interesting comment. How can +1/-1 give different results? – mherzog Nov 14 '20 at 14:08
  • 2
    @mherzog A CPU must do at least three operations in order to increment a shared variable; fetch the value from memory, compute a new value, store the new value back. If it two threads fetch the same value in the first step, then they will both write back the same new value, and the variable will effectively only be incremented once. Synchronization primitives (e.g., a mutex) are necessary to prevent that from happening. In CPython, [the GIL](https://wiki.python.org/moin/GlobalInterpreterLock) is a mutex that gets locked and unlocked for each Python statement. Other languages don't have a GIL. – Solomon Slow Nov 14 '20 at 16:37