Is there a more efficient threading lock?

Michael Speer knomenet at gmail.com
Sun Feb 26 22:19:55 EST 2023


I wanted to provide an example that your claimed atomicity is simply wrong,
but I found there is something different in the 3.10+ cpython
implementations.

I've tested the code at the bottom of this message using a few docker
python images, and it appears there is a difference starting in 3.10.0

python3.8
EXPECTED 2560000000
ACTUAL   84533137
python:3.9
EXPECTED 2560000000
ACTUAL   95311773
python:3.10 (.8)
EXPECTED 2560000000
ACTUAL   2560000000

just to see if there was a specific sub-version of 3.10 that added it
python:3.10.0
EXPECTED 2560000000
ACTUAL   2560000000

nope, from the start of 3.10 this is happening

the only difference in the bytecode I see is 3.10 adds SETUP_LOOP and
POP_BLOCK around the for loop

I don't see anything different in the long c code that I would expect would
cause this.

AFAICT the inplace add is null for longs and so should revert to the
long_add that always creates a new integer in x_add

another test
python:3.11
EXPECTED 2560000000
ACTUAL   2560000000

I'm not sure where the difference is at the moment. I didn't see anything
in the release notes given a quick glance.

I do agree that you shouldn't depend on this unless you find a written
guarantee of the behavior, as it is likely an implementation quirk of some
kind

--[code]--

import threading

UPDATES = 10000000
THREADS = 256

vv = 0

def update_x_times( xx ):
    for _ in range( xx ):
        global vv
        vv += 1

def main():
    tts = []
    for _ in range( THREADS ):
        tts.append( threading.Thread( target = update_x_times, args =
(UPDATES,) ) )

    for tt in tts:
        tt.start()

    for tt in tts:
        tt.join()

    print( 'EXPECTED', UPDATES * THREADS )
    print( 'ACTUAL  ', vv )

if __name__ == '__main__':
    main()

On Sun, Feb 26, 2023 at 6:35 PM Jon Ribbens via Python-list <
python-list at python.org> wrote:

> On 2023-02-26, Barry Scott <barry at barrys-emacs.org> wrote:
> > On 25/02/2023 23:45, Jon Ribbens via Python-list wrote:
> >> I think it is the case that x += 1 is atomic but foo.x += 1 is not.
> >
> > No that is not true, and has never been true.
> >
> >:>>> def x(a):
> >:...    a += 1
> >:...
> >:>>>
> >:>>> dis.dis(x)
> >   1           0 RESUME                   0
> >
> >   2           2 LOAD_FAST                0 (a)
> >               4 LOAD_CONST               1 (1)
> >               6 BINARY_OP               13 (+=)
> >              10 STORE_FAST               0 (a)
> >              12 LOAD_CONST               0 (None)
> >              14 RETURN_VALUE
> >:>>>
> >
> > As you can see there are 4 byte code ops executed.
> >
> > Python's eval loop can switch to another thread between any of them.
> >
> > Its is not true that the GIL provides atomic operations in python.
>
> That's oversimplifying to the point of falsehood (just as the opposite
> would be too). And: see my other reply in this thread just now - if the
> GIL isn't making "x += 1" atomic, something else is.
> --
> https://mail.python.org/mailman/listinfo/python-list
>


More information about the Python-list mailing list