Python中的+ =运算符是线程安全的吗?
我想为实验创build一个非线程安全的代码块,这些是2个线程将要调用的函数。
c = 0 def increment(): c += 1 def decrement(): c -= 1
这个代码线程安全吗?
如果没有,我可以理解为什么它不是线程安全的,哪种语句通常会导致非线程安全的操作。
如果它是线程安全的,我怎样才能使它显式非线程安全?
由于GIL,单一操作码是线程安全的,但没有别的:
import time class something(object): def __init__(self,c): self.c=c def inc(self): new = self.c+1 # if the thread is interrupted by another inc() call its result is wrong time.sleep(0.001) # sleep makes the os continue another thread self.c = new x = something(0) import threading for _ in range(10000): threading.Thread(target=x.inc).start() print xc # ~900 here, instead of 10000
每个由多个线程共享的资源都必须有一个锁。
不,这个代码是绝对的,显然不是线程安全的。
import threading i = 0 def test(): global i for x in range(100000): i += 1 threads = [threading.Thread(target=test) for t in range(10)] for t in threads: t.start() for t in threads: t.join() assert i == 1000000, i
一贯失败。
我+ = 1parsing为四个操作码:加载我,加载1,添加两个,并将其存储回我。 Python解释器每100个操作码切换活动线程(通过从一个线程释放GIL,以便另一个线程可以拥有它)。 (这两个都是实现的细节。)竞争条件发生在加载和存储之间发生100-操作码抢占时,允许另一个线程开始递增计数器。 当它回到挂起的线程时,它将继续使用旧的“i”值,同时解除其他线程运行的增量。
使它线程安全是直接的; 添加locking:
#!/usr/bin/python import threading i = 0 i_lock = threading.Lock() def test(): global i i_lock.acquire() try: for x in range(100000): i += 1 finally: i_lock.release() threads = [threading.Thread(target=test) for t in range(10)] for t in threads: t.start() for t in threads: t.join() assert i == 1000000, i
(注意:你需要在每个函数中使用global c
来使你的代码工作。)
这个代码线程安全吗?
在CPython中只有一个字节码指令是'primefaces'的,即使所涉及的值是简单整数,a +=
也不会导致单个操作码:
>>> c= 0 >>> def inc(): ... global c ... c+= 1 >>> import dis >>> dis.dis(inc) 3 0 LOAD_GLOBAL 0 (c) 3 LOAD_CONST 1 (1) 6 INPLACE_ADD 7 STORE_GLOBAL 0 (c) 10 LOAD_CONST 0 (None) 13 RETURN_VALUE
因此,一个线程可以用c和1加载索引6,放弃GIL并让另一个线程进入,执行一个inc
并hibernate,将GIL返回到第一个线程,该线程现在具有错误的值。
在任何情况下,什么是primefaces是你不应该依赖的实现细节。 在未来的CPython版本中,字节码可能会发生变化,而在不依赖GIL的其他Python实现中,结果将会完全不同。 如果你需要线程安全,你需要一个locking机制。
为了确保我推荐使用锁:
import threading class ThreadSafeCounter(): def __init__(self): self.lock = threading.Lock() self.counter=0 def increment(self): with self.lock: self.counter+=1 def decrement(self): with self.lock: self.counter-=1
同步装饰器也可以帮助保持代码容易阅读。
很容易certificate你的代码不是线程安全的 。 您可以通过在关键部分使用睡眠来增加看到竞争条件的可能性(这只是模拟一个慢速的CPU)。 但是,如果您足够长的时间运行代码,最终应该看到竞争条件。
from time import sleep c = 0 def increment(): global c c_ = c sleep(0.1) c = c_ + 1 def decrement(): global c c_ = c sleep(0.1) c = c_ - 1
简短的回答:不。
长答案:一般不会。
虽然CPython的GIL使单个操作符是线程安全的 ,但这不是一般的行为。 你可能不会认为像加法这样简单的操作就是一个primefaces指令。 当另一个线程运行时,添加可能只有一半。
只要你的函数在多个操作码中访问一个variables,你的线程安全就没有了。 如果将函数体包装在锁中 ,则可以生成线程安全性。 但请注意,锁的计算成本可能很高,并可能产生死锁。
如果你真的想让你的代码不是线程安全的,并且很有可能在没有你尝试一万次的时候实际发生“坏”事情(或者一次你真的不想“坏”的事情发生),你可以用明确的睡眠来“抖动”你的代码:
def íncrement(): global c x = c from time import sleep sleep(0.1) c = x + 1
你确定函数递增和递减执行没有任何错误?
我认为它应该引发一个UnboundLocalError,因为你必须明确地告诉Python你想使用名为'c'的全局variables。
所以改变增量(也递减)如下:
def increment(): global c c += 1
我认为你的代码是不安全的。 这篇关于Python中的线程同步机制的文章可能会有所帮助。