说到线程,我知道你必须确保你没有在另一个线程编辑它的同时编辑变量,因为你的更改可能会丢失(例如,当递增计数器时)
这同样适用于词典吗?或者是字典是变量的集合?
如果每个线程都要锁定字典,那么它会大大降低程序的速度,而每个线程只需要对它自己的小字典进行写访问。
如果不可能,python中是否有某种变量变量,比如php?
答案 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)
我认为你误解了整个线程的安全问题。它不是变量(或变量变量 - 无论如何都是非常糟糕的,并且就像在其他情况下一样毫无意义 - 更不用说有害了 - 但是关于 - 例如,线程有很多讨厌的方法可以去错误;它们都来自于在重叠时间从多个线程中访问可变的东西 - 这个:
它也适用于字典和变量变量(这只是一个可怕的,可怕的语言级别的字符串实现,只使用字符串键)。唯一的解决方案是不使用共享状态开始(函数式语言通过阻止甚至完全不允许可变性来实现这一点,并且它适用于它们)或者为共享的所有内容添加某种锁定(很难做到,但是如果你得到的话)它是对的,至少它正常工作)。如果没有两个线程分享该词典中的任何内容,那么你很好 - 但是你应该分开所有内容,(更多一点)确保他们真的不分享任何内容。
答案 2 :(得分:0)
您需要做的是不允许线程直接访问共享数据结构,而是使用保证mutex互斥的内容来包装对它的访问。
访问原始结构看起来相同(shared[id] = value
)需要更多工作,但不是那么多。