Reputation: 13840
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
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
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 codeprint
can lead to multiple calls to a write()
functionwrite
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