从不同的线程修改Python字典

ske*_*rit 13 python variables multithreading dictionary locking

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

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

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

如果不可能,python中是否有某种变量变量,就像在php中一样?

mou*_*uad 23

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

让我们更一般:

"原子操作"是什么意思?

来自维基百科:

在并发编程中,如果系统的其余部分在瞬间发生,则操作(或操作集)是原子的,可线性化的,不可分割的或不可中断的.原子性是与并发进程隔离的保证.

现在这在Python中意味着什么?

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

这是为什么???

因为Python(CPython)使用全局解释器锁(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        
Run Code Online (Sandbox Code Playgroud)

那么字典怎么样?

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

d[x] = y
d.update(d2)
d.keys()
Run Code Online (Sandbox Code Playgroud)

你自己看:

>>> 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   
Run Code Online (Sandbox Code Playgroud)

请参阅此内容以了解STORE_SUBSCR的作用.

但正如你所看到的,这并非完全正确,因为这个操作:

             ...
  4          12 LOAD_FAST                2 (y)
             15 LOAD_FAST                0 (d)
             18 LOAD_FAST                1 (x)
             ...
Run Code Online (Sandbox Code Playgroud)

可以使整个操作不是原子的.为什么?假设变量x也可以被另一个线程更改......或者你想要另一个线程来清除你的字典...我们可以在出现错误的情况下列出许多情况,因此它很复杂!所以我们将在这里应用墨菲定律:"任何可能出错的东西,都会出错".

所以现在怎么办?

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

import threading

mylock = threading.RLock()

def atomic_operation():
    with mylock:
        print "operation are now atomic"
Run Code Online (Sandbox Code Playgroud)


小智 7

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

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

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