gpt4 book ai didi

python - Python 2.X 中的 `print` 内置函数是原子函数吗?

转载 作者:行者123 更新时间:2023-12-01 02:46:21 25 4
gpt4 key购买 nike

本周我一直在探索 Python 中线程的内部实现。令人惊奇的是,我每天都对自己的无知感到惊讶;不知道我想知道什么,这就是让我发痒的原因。

我注意到我在 Python 2.7 下作为多线程应用程序运行的一段代码中有一些奇怪的地方。我们都知道Python 2.7默认在100条虚拟指令后进行线程切换。调用函数就是一条虚拟指令,例如:

>>> from __future__ import print_function
>>> def x(): print('a')
...
>>> dis.dis(x)
1 0 LOAD_GLOBAL 0 (print)
3 LOAD_CONST 1 ('a')
6 CALL_FUNCTION 1
9 POP_TOP
10 LOAD_CONST 0 (None)
13 RETURN_VALUE

如您所见,在加载全局 print 和加载常量 a 后,函数被调用。因此,调用函数是原子的,因为它是通过一条指令完成的。因此,在多线程程序中,要么函数(此处为 print)运行,要么“正在运行”的线程在函数获得要运行的更改之前被中断。也就是说,如果 LOAD_GLOBALLOAD_CONST 之间发生上下文切换,指令 CALL_FUNCTION 将不会运行。

请记住,在上面的代码中,我使用的是 from __future__ import print_function,我现在实际上是在调用内置函数,而不是 print 语句。让我们看一下函数 x 的字节码,但这次使用 print 语句:

>>> def x(): print "a"          # print stmt
...
>>> dis.dis(x)
1 0 LOAD_CONST 1 ('a')
3 PRINT_ITEM
4 PRINT_NEWLINE
5 LOAD_CONST 0 (None)
8 RETURN_VALUE

在这种情况下,LOAD_CONSTPRINT_ITEM 之间很可能发生线程上下文切换,从而有效地阻止 PRINT_NEWLINE 指令执行。因此,如果您有一个像这样的多线程程序(借用《Python 编程》第 4 版并稍作修改):

def counter(myId, count):
for i in range(count):
time.sleep(1)
print ('[%s] => %s' % (myId, i)) #print (stmt) 2.X

for i in range(5):
thread.start_new_thread(counter, (i, 5))

time.sleep(6) # don't quit early so other threads don't die

输出可能会或可能不会如下所示,具体取决于线程的切换方式:

[0] => 0
[3] => 0[1] => 0
[4] => 0
[2] => 0
...many more...

使用print语句就可以了。

如果我们使用内置 print 函数更改 print 语句,会发生什么?让我们看看:

from __future__ import print_function
def counter(myId, count):
for i in range(count):
time.sleep(1)

print('[%s] => %s' % (myId, i)) #print builtin (func)

for i in range(5):
thread.start_new_thread(counter, (i, 5))

time.sleep(6)

如果您多次运行此脚本足够长的时间,您将看到如下内容:

[4] => 0
[3] => 0[1] => 0
[2] => 0
[0] => 0
...many more...

鉴于上述所有解释,这怎么可能? print 现在是一个函数,为什么它打印传入的字符串而不是新行? print 在打印字符串的末尾打印 end 的值,默认设置为 \n。本质上,对函数的调用是原子的,它到底是怎么被中断的?

让我们大吃一惊:

def counter(myId, count):
for i in range(count):
time.sleep(1)
#sys.stdout.write('[%s] => %s\n' % (myId, i))
print('[%s] => %s\n' % (myId, i), end='')

for i in range(5):
thread.start_new_thread(counter, (i, 5))

time.sleep(6)

现在总是打印新行,不再出现困惑的输出:

[1] => 0
[2] => 0
[0] => 0
[4] => 0
...many more...

在字符串中添加 \n 现在显然证明 print 函数不是原子函数(即使它是一个函数),本质上它只是充当print 语句。然而,dis.dis 语无伦次或愚蠢地告诉我们它是一个简单的函数,因此是一个原子操作?!

注意:我从不依赖线程的顺序或时间来保证应用程序正常工作。坦率地说,这仅用于测试目的,适合像我这样的极客。

最佳答案

你的问题是基于中心前提

Calling a function therefore is atomic as it's done with a single instruction.

这是完全错误的。

首先,执行CALL_FUNCTION操作码可能涉及执行额外的字节码。最明显的情况是执行的函数是用 Python 编写的,但即使是内置函数也可以自由调用其他可能用 Python 编写的代码。例如,print 调用 __str__write 方法。

其次,即使在 C 代码中间,Python 也可以自由地释放 GIL。它通常对 I/O 和其他可能需要一段时间而不需要执行 Python API 调用的操作执行此操作。 Python 2.7 file object implementation 中有 23 次使用 FILE_BEGIN_ALLOW_THREADSPy_BEGIN_ALLOW_THREADS 宏。单独的一个,包括 file.write 实现中的一个,print 依赖于该实现。

关于python - Python 2.X 中的 `print` 内置函数是原子函数吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45225311/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com