Shuzheng
Shuzheng

Reputation: 13840

Is print() atomic in Python3?

I know the Global Interpreter Lock (GIL) only switches among threads between bytecode instructions, so that most operations on lists and dictionaries are atomic etc.

But does print() correspond to a single bytecode instruction in Python3, so that it can be considered an atomic operation?

Bonus: What happens, if one thread modifies (e.g. insert) into a list that another thread is for-looping?

http://effbot.org/pyfaq/what-kinds-of-global-value-mutation-are-thread-safe.htm

Upvotes: 2

Views: 953

Answers (2)

Amadan
Amadan

Reputation: 198314

Look for yourself:

>>> import dis
>>> dis.dis(lambda: print("foo"))
  1           0 LOAD_GLOBAL              0 (print)
              2 LOAD_CONST               1 ('foo')
              4 CALL_FUNCTION            1
              6 RETURN_VALUE

So there's just CALL_FUNCTION, nothing more... Unless print itself is complex?

>>> dis.dis(print)
TypeError: don't know how to disassemble builtin_function_or_method objects

So, print is a built-in, written in C, and calling it is just the one bytecode (if you disregard placing arguments onto stack to prepare for the call).

EDIT: But see the results of deeper digging by Anthony Sottile!

Upvotes: 4

anthony sottile
anthony sottile

Reputation: 69914

(note this answer assumes cpython implementation details. GIL is a cpython implementation detail as well so I think this is ok)

print is implemented in C -- while it is true that it is only a single bytecode as @Amadan mentions, there is potential for GIL release

Here's (important bits of) the source for print():

    for (i = 0; i < nargs; i++) {
        if (i > 0) {
            if (sep == NULL)
                err = PyFile_WriteString(" ", file);
            else
                err = PyFile_WriteObject(sep, file,
                                         Py_PRINT_RAW);
            if (err)
                return NULL;
        }
        err = PyFile_WriteObject(args[i], file, Py_PRINT_RAW);
        if (err)
            return NULL;
    }

    if (end == NULL)
        err = PyFile_WriteString("\n", file);
    else
        err = PyFile_WriteObject(end, file, Py_PRINT_RAW);
    if (err)
        return NULL;

The important thing to note here is that there are multiple calls to PyFile_Write*. Each of those ~usually invoke some sort of write() method. And where do the write methods end up in? Usually _Py_write:

The important bits from the docstring:

Release the GIL to call write(). The caller must hold the GIL.

With this information:

  • print in cpython is a single byte code
  • a call to print can lead to multiple calls to a write() function
  • In almost all cases, write may release the GIL.

If if it's easier to convince yourself of this, consider a custom file object:

>>> class MyFile(object):
...     def write(self, obj):
...         sys.stdout.write(f'called! {obj!r}\n')
...
>>> import sys
>>> print('hi', file=MyFile())
called! 'hi'
called! '\n'

Upvotes: 3

Related Questions