线程状态和全局解释器锁
Python 解释器不是完全线程安全的。当前线程想要安全访问 Python 对象的前提是获取用以支持多线程安全的全局锁。没有这个锁,甚至多线程程序中最简单的操作都会发生问题。例如,两个线程同时增加一个对象的引用计数,该引用计数可能只增加了一次而非两次。
因此,存在一个规则:只有获得了全局解释器锁的线程才能操作 Python 对象或者调用 Python/C API 函数。为了支持多线程 Python 编程,解释器有规律的释放和回收锁——默认情况下,每100字节指令集循环一次(可以通过sys.setcheckinterval()设置)。类似文件读写之类的 i/o 片也会随锁释放和回收,这样其它的线程在请求 I/O 操作的线程等待I/O操作完成的时候也可以运行。
全局锁事实上并不能避免你程序中的问题. 多个线程尝试访问相同的数据会导致异常 状态. 例如以下的代码:
def getitem(key): item = cache.get(key) if item is None: # not in cache; create a new one item = create_new_item(key) cache[key] = item return item
如果不同的线程先后使用相同的 key 调用这里的 getitem
方法, 那么它们很可能会导致相同的参数调用两次 create_new_item
. 大多时候这样做没有问题, 但在某些时候会导致严重错误.
更不同说修改相同的数据(共享数据)也可能出现无法预料的结果。
假如两个线程对象t1和t2都要对数值num=0进行增1运算,那么t1和t2都各对num修改10次的话,那么num最终的结果应该为20。但是如果当t1取得num的值时(假如此时num为0),系统把t1调度为“sleeping”状态,而此时t2转换为“running”状态,此时t2获得的num的值也为0,然后他把num+1的值1赋给num。系统又把t2转化为“sleeping”状态,t1为“running”状态,由于t1已经得到num值为0,所以他也把num+1的值赋给了num为1。本来是2次增1运行,结果却是num只增了1次。类似这样的情况在多线程同时执行的时候是有可能发生的。所以为了防止这类情况的出现就要使用线程同步机制。
不过你可以使用 lock objects 来同步线程. 一个线程只能拥有一个 lock object , 这样就可以确保某个时刻 只有一个线程执行 getitem 函数.