[Python-Dev] Importance of "async" keyword

Chris Angelico rosuav at gmail.com
Fri Jun 26 17:10:33 CEST 2015


On Sat, Jun 27, 2015 at 12:51 AM, Paul Sokolovsky <pmiscml at gmail.com> wrote:
> Some say "convenient", others say "dangerous".
>
> Consider:
>
> buf = bytearray(MULTIMEGABYTE)
>
> In one function you do:
>
> socket.write(buf),
>
> in another function (coroutine), you keep mutating buf.
>
> So, currently in Python you know if you do:
>
> socket.write(buf)
>
> Then you know it will finish without interruptions for entire buffer.
> And if you write:
>
> await socket.write(buf)
>
> then you know there may be interruption points inside socket.write(),
> in particular something else may mutate it while it's being written.
> With threads, you always have to assume this last scenario, and do
> extra effort to protect against it (mutexes and stuff).

Hmm. I'm not sure how this is a problem; whether you use 'await' or
not, using a mutable object from two separate threads or coroutines is
going to have that effect.

The way I'm seeing it, coroutines are like cooperatively-switched
threads; you don't have to worry about certain operations being
interrupted (particularly low-level ones like refcount changes or list
growth), but any time you hit an 'await', you have to assume a context
switch. That's all very well, but I'm not sure it's that big a problem
to accept that any call could context-switch; atomicity is already a
concern in other cases, which is why we have principles like EAFP
rather than LBYL.

There's clearly some benefit to being able to assume that certain
operations are uninterruptible (because there's no 'await' anywhere in
them), but are they truly so? Signals can already interrupt something
_anywhere_:

>>> import signal
>>> def mutate(sig, frm):
...     print("Mutating the global!")
...     lst[5] = 0
...
>>> signal.signal(signal.SIGALRM, mutate)
<Handlers.SIG_DFL: 0>
>>> lst = [1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> def spin():
...     while True:
...         lst[0] += lst[5]
...         lst[2] += lst[0]
...         lst[4] += lst[6]
...         lst[6] -= lst[2]
...         if not lst[5]:
...             print("Huh???")
...             break
...
>>> signal.alarm(2)
0
>>> spin()
Mutating the global!
Huh???

Any time you're using mutable globals, you have to assume that they
can be mutated out from under you. Coroutines don't change this, so is
there anything gained by knowing where you can't be interrupted by one
specific possible context switch?

ChrisA


More information about the Python-Dev mailing list