变量交换在Python中是否保证是原子的?
根据以下链接的内容:http://docs.python.org/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe
我想知道下面这段代码:
(x, y) = (y, x)
在cPython中是否会被保证是原子操作。(x和y都是Python变量)
2 个回答
是的,确实会。
我错了。
Kragen Sitaker 写道:
有人建议使用这个方法
spam, eggs = eggs, spam
来实现线程安全的交换。这真的有效吗?(...)
如果这个线程在第一次加载和最后一次存储之间失去控制,
另一个线程可能会把一个值存储到“b”中,
这样这个值就会丢失。
这不是说没有什么能阻止这种情况发生,对吧?没错。一般来说,甚至简单的赋值操作也不一定是线程安全的,因为执行赋值可能会调用对象上的特殊方法,而这些方法本身可能需要进行多个操作。希望这个对象会在内部锁定它的“状态”值,但这并不总是如此。
不过,线程安全的具体含义取决于特定的应用,因为在我看来,线程安全有很多不同的层次,所以很难简单地谈论“线程安全”。Python解释器能给你提供的唯一保障是,内置数据类型应该能够避免内部损坏,即使在使用原生线程的情况下。换句话说,如果两个线程分别有
a=0xff
和a=0xff00
,那么最终的a会是其中一个值,而不会意外变成0xffff
,这在某些其他语言中如果没有保护可能会发生。话虽如此,Python的执行方式通常允许你在不进行正式锁定的情况下做很多事情,只要你愿意冒点风险,并且对实际使用的对象有隐含的依赖关系。之前在c.l.p有过不错的讨论——可以在groups.google.com上搜索“关键区和互斥锁”这个话题。
就我个人而言,我会明确锁定共享状态(或者使用专门设计用于在线程之间正确交换共享信息的结构,比如
Queue.Queue
)在任何多线程应用中。对我来说,这是对未来维护和演变的最佳保护。-- -- David
我们来看看:
>>> x = 1
>>> y = 2
>>> def swap_xy():
... global x, y
... (x, y) = (y, x)
...
>>> dis.dis(swap_xy)
3 0 LOAD_GLOBAL 0 (y)
3 LOAD_GLOBAL 1 (x)
6 ROT_TWO
7 STORE_GLOBAL 1 (x)
10 STORE_GLOBAL 0 (y)
13 LOAD_CONST 0 (None)
16 RETURN_VALUE
看起来它们并不是原子操作:在LOAD_GLOBAL
字节码之间,x和y的值可能会被其他线程修改,无论是在ROT_TWO
之前还是之后,以及在STORE_GLOBAL
字节码之间。
如果你想要原子性地交换两个变量,你需要使用锁或者互斥量。
对于那些想要实证的人:
>>> def swap_xy_repeatedly():
... while 1:
... swap_xy()
... if x == y:
... # If all swaps are atomic, there will never be a time when x == y.
... # (of course, this depends on "if x == y" being atomic, which it isn't;
... # but if "if x == y" isn't atomic, what hope have we for the more complex
... # "x, y = y, x"?)
... print 'non-atomic swap detected'
... break
...
>>> t1 = threading.Thread(target=swap_xy_repeatedly)
>>> t2 = threading.Thread(target=swap_xy_repeatedly)
>>> t1.start()
>>> t2.start()
>>> non-atomic swap detected