变量交换在Python中是否保证是原子的?

32 投票
2 回答
5943 浏览
提问于 2025-04-15 21:30

根据以下链接的内容: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 个回答

4

是的,确实会。

我错了

Kragen Sitaker 写道:

有人建议使用这个方法

spam, eggs = eggs, spam

来实现线程安全的交换。这真的有效吗?(...)
如果这个线程在第一次加载和最后一次存储之间失去控制,
另一个线程可能会把一个值存储到“b”中,
这样这个值就会丢失。
这不是说没有什么能阻止这种情况发生,对吧?

没错。一般来说,甚至简单的赋值操作也不一定是线程安全的,因为执行赋值可能会调用对象上的特殊方法,而这些方法本身可能需要进行多个操作。希望这个对象会在内部锁定它的“状态”值,但这并不总是如此。

不过,线程安全的具体含义取决于特定的应用,因为在我看来,线程安全有很多不同的层次,所以很难简单地谈论“线程安全”。Python解释器能给你提供的唯一保障是,内置数据类型应该能够避免内部损坏,即使在使用原生线程的情况下。换句话说,如果两个线程分别有 a=0xffa=0xff00,那么最终的a会是其中一个值,而不会意外变成 0xffff,这在某些其他语言中如果没有保护可能会发生。

话虽如此,Python的执行方式通常允许你在不进行正式锁定的情况下做很多事情,只要你愿意冒点风险,并且对实际使用的对象有隐含的依赖关系。之前在c.l.p有过不错的讨论——可以在groups.google.com上搜索“关键区和互斥锁”这个话题。

就我个人而言,我会明确锁定共享状态(或者使用专门设计用于在线程之间正确交换共享信息的结构,比如 Queue.Queue)在任何多线程应用中。对我来说,这是对未来维护和演变的最佳保护。

-- -- David

58

我们来看看:

>>> 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

撰写回答