从不同的线程修改Python字典

时间:2010-12-27 22:39:55

标签: python multithreading variables dictionary locking

说到线程,我知道你必须确保你没有在另一个线程编辑它的同时编辑变量,因为你的更改可能会丢失(例如,当递增计数器时)

这同样适用于词典吗?或者是字典是变量的集合?

如果每个线程都要锁定字典,那么它会大大降低程序的速度,而每个线程只需要对它自己的小字典进行写访问。

如果不可能,python中是否有某种变量变量,比如php?

3 个答案:

答案 0 :(得分:23)

  

这同样适用于词典吗?或者是字典是变量的集合?

让我们更一般:

“原子操作”是什么意思?

来自Wikipedia

  

在并发编程中,   操作(或操作集)是   原子的,可线性化的,不可分割的或   如果看起来像是不间断的   系统的其余部分发生   瞬间。原子性是一种   保证与并发隔离   过程

现在在Python中意味着什么?

这意味着每个字节码指令都是原子的(至少对于Python< 3.2,在新的GIL之前)。

为什么???

因为Python(CPython)使用Global Interpreter Lock (GIL)。 CPython解释器使用锁来确保一次只有一个线程在解释器中运行,并使用“检查间隔”(参见sys.getcheckinterval())来了解在线程之间切换之前要执行多少字节码指令(通过默认设置为100)。

所以现在这意味着什么?

这意味着只能由一个字节码指令表示的操作是原子的。例如,递增变量是而不是原子,因为操作是在三个字节码指令中完成的:

>>> import dis

>>> def f(a):
        a += 1

>>> dis.dis(f)
  2           0 LOAD_FAST                0 (a)
              3 LOAD_CONST               1 (1)      <<<<<<<<<<<< Operation 1 Load
              6 INPLACE_ADD                         <<<<<<<<<<<< Operation 2 iadd
              7 STORE_FAST               0 (a)      <<<<<<<<<<<< Operation 3 store
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE        

那么词典呢?

有些操作是原子的;例如,此操作是原子的:

d[x] = y
d.update(d2)
d.keys()

亲眼看看:

>>> def f(d):
        x = 1
        y = 1
        d[x] = y

>>> dis.dis(f)
  2           0 LOAD_CONST               1 (1)
              3 STORE_FAST               1 (x)

  3           6 LOAD_CONST               1 (1)
              9 STORE_FAST               2 (y)

  4          12 LOAD_FAST                2 (y)
             15 LOAD_FAST                0 (d)
             18 LOAD_FAST                1 (x)
             21 STORE_SUBSCR                      <<<<<<<<<<< One operation 
             22 LOAD_CONST               0 (None)
             25 RETURN_VALUE   

请参阅this以了解STORE_SUBSCR的作用。

但是如你所见,这并非完全正确,因为这个操作:

             ...
  4          12 LOAD_FAST                2 (y)
             15 LOAD_FAST                0 (d)
             18 LOAD_FAST                1 (x)
             ...

可以使整个操作不是原子的。为什么?假设变量x也可以被另一个线程更改......或者你想要另一个线程来清除你的字典...我们可以在出现错误时列出很多情况,所以它很复杂!所以我们在这里申请Murphy's Law:“任何可能出错的地方都会出错”。

那么现在呢?

如果您仍想在线程之间共享变量,请使用锁定:

import threading

mylock = threading.RLock()

def atomic_operation():
    with mylock:
        print "operation are now atomic"

答案 1 :(得分:7)

我认为你误解了整个线程的安全问题。它不是变量(或变量变量 - 无论如何都是非常糟糕的,并且就像在其他情况下一样毫无意义 - 更不用说有害了 - 但是关于 - 例如,线程有很多讨厌的方法可以去错误;它们都来自于在重叠时间从多个线程中访问可变的东西 - 这个:

  • 线程N从源(内存或磁盘上的某个位置 - 变量,字典中的插槽,文件,几乎任何可变的内容)获取数据。
  • 线程M从源
  • 获取数据
  • 线程N修改数据
  • 线程M修改数据
  • 线程N用修改后的数据覆盖源
  • 线程M用修改后的数据覆盖源
  • 结果:线程N的修改丢失/新的共享值不考虑线程N的修改

它也适用于字典和变量变量(这只是一个可怕的,可怕的语言级别的字符串实现,只使用字符串键)。唯一的解决方案是不使用共享状态开始(函数式语言通过阻止甚至完全不允许可变性来实现这一点,并且它适用于它们)或者为共享的所有内容添加某种锁定(很难做到,但是如果你得到的话)它是对的,至少它正常工作)。如果没有两个线程分享该词典中的任何内容,那么你很好 - 但是你应该分开所有内容,(更多一点)确保他们真的不分享任何内容。

答案 2 :(得分:0)

您需要做的是不允许线程直接访问共享数据结构,而是使用保证mutex互斥的内容来包装对它的访问。

访问原始结构看起来相同(shared[id] = value)需要更多工作,但不是那么多。