Python变量赋值是原子的吗?

时间:2010-02-18 18:21:00

标签: python signals

假设我使用signal处理程序来处理间隔计时器。

def _aHandler(signum, _):
  global SomeGlobalVariable
  SomeGlobalVariable=True

我可以设置SomeGlobalVariable而不用担心,在设置SomeGlobalVariable(即Python VM正在执行字节码来设置变量)的不太可能的情况下,信号处理程序中的赋值将会中断什么? (即元稳定状态)

更新:我特别感兴趣的是在处理程序之外进行“复合赋值”。

(也许我在想“低级别”,这一切都在Python中得到了解决......来自嵌入式系统的背景,我不时有这些冲动)

4 个答案:

答案 0 :(得分:17)

对简单变量的简单分配是“原子”AKA线程安全(诸如+=之类的复合赋值或对象的项目或属性的赋值不必是,但是您的示例是对简单(尽管是全局的)的简单赋值,变量,因此是安全的。)

答案 1 :(得分:1)

复合赋值包括三个步骤:read-update-write。如果运行另一个线程并且在读取发生之后但在写入之前将新值写入该位置,则这是竞争条件。在这种情况下,一个陈旧的值正在被更新并写回,这将破坏另一个线程写入的任何新值。在Python中,涉及执行单字节代码的任何事情都应该是原子的,但复合赋值不符合这个标准。使用锁。

答案 2 :(得分:1)

Google的样式指南不建议这样做

我并不是说Google风格指南是最终的真理,但是rationale in the "Threading" section提供了一些见识(突出之处是我的观点):

  

不要依赖内置类型的原子性。

     

虽然Python的内置数据类型(例如字典)似乎具有原子操作,但在某些极端情况下它们不是原子操作(例如,如果__hash____eq__被实现为Python方法)和不应该依赖它们的原子性。 您也不应该依赖于原子变量赋值(因为这又取决于字典)。

     

使用Queue模块的Queue数据类型作为在线程之间通信数据的首选方法。否则,请使用线程模块及其锁定原语。了解如何正确使用条件变量,以便您可以使用threading.Condition而不是使用较低级别的锁。

所以我的解释是,在Python中,所有内容都类似于dict,并且当您在后端a = b处进行globals['a'] = b时,这很糟糕,因为dict不一定是线程安全的。

对于单个变量,Queue并不理想,因为我们希望它仅容纳一个元素,并且我无法在stdlib中找到能够自动同步.set()的完美容器。方法。所以现在我只做:

import threading

myvar = 0
myvar_lock = threading.Lock()
with myvar_lock:
    myvar = 1
with myvar_lock:
    myvar = 2

有趣的是,Martelli does not seem to mind推荐了Google风格指南:-)(他在Google工作)

我想知道CPython GIL是否对以下问题有影响:What is the global interpreter lock (GIL) in CPython?

该线程还表明CPython字典是线程安全的,包括以下明确引用它的词汇表引用https://docs.python.org/3/glossary.html#term-global-interpreter-lock

  

这通过使对象模型(包括关键的内置类型,如dict)隐式地安全地防止并发访问,从而简化了CPython的实现。

答案 3 :(得分:0)

您可以尝试import numpy as np import cv2 from matplotlib import pyplot as plt img = cv2.imread("yourimage") b,g,r = cv2.split(img) # get b,g,r rgb_img = cv2.merge([r,g,b]) # switch it to rgb # Denoising dst = cv2.fastNlMeansDenoisingColored(img,None,10,10,7,21) b,g,r = cv2.split(dst) # get b,g,r rgb_dst = cv2.merge([r,g,b]) # switch it to rgb plt.subplot(211),plt.imshow(rgb_img) plt.subplot(212),plt.imshow(rgb_dst) plt.show() 查看基础字节码。

dis

产生字节码:

import dis

def foo():
    a = 1


dis.dis(foo)

因此,该分配是单个python字节码(指令2),由于它一次执行一个字节码,因此在CPython中是原子的。

而添加一个# a = 1 5 0 LOAD_CONST 1 (1) 2 STORE_FAST 0 (a)

a += 1

产生字节码:

def foo():
    a = 1
    a += 1

# a+=1 6 4 LOAD_FAST 0 (a) 6 LOAD_CONST 1 (1) 8 INPLACE_ADD 10 STORE_FAST 0 (a) 对应4条指令,这不是原子指令。