Python中执行not in判断时列表大小变动是否会产生异常或UB?
问题复现代码
items = [ 'A', 'B' ] def func1(): while True: if 'C' not in items: # do something def func2(): while True: items.clear() items.append('A') items.append('B') def main(): start_new_thread(func1) start_new_thread(func2)
核心结论
- 从Python语言规范层面定义,无同步保护的多线程同时读写共享可变内置容器,属于未定义行为(UB),不同Python解释器实现的表现没有统一标准
- 主流CPython解释器的表现分为两种场景:元素为内置类型时不会抛出异常,仅存在逻辑结果不确定问题;元素为自定义类型时存在抛出异常的可能
具体原理说明
- CPython的GIL(全局解释器锁)保证了同一时间只有一个线程执行Python字节码,单个纯C实现的内置方法执行全程不会被线程调度打断。你示例中列表元素为字符串,列表的
__contains__(对应not in判断)、clear()、append()、字符串的相等判断全都是纯C实现的操作,因此not in遍历过程中不会被func2的修改操作打断,不会出现遍历中途列表长度变化的情况,不会抛出异常。 - 即便不会抛出异常,你依然会遇到逻辑层面的未定义问题:你无法确定
'C' not in items的执行时机,是在func2执行完clear()之后、两次append()之前,还是两次append()执行完成之后,不同时机的判断结果很可能不符合业务预期(如果业务逻辑依赖列表固定有两个元素的话)。 - 如果列表中存储的是自定义类的实例,
not in遍历过程中会调用元素的__eq__方法,该方法如果包含Python代码,GIL会在字节码执行间隙释放,此时就有可能被切换到func2线程修改列表长度,切回后继续遍历就有可能抛出IndexError异常。
注意:不要依赖CPython的GIL特性作为多线程同步手段,这种代码兼容性极差,切换到PyPy等其他Python实现时行为可能完全不符合预期,多线程读写共享可变容器必须显式加锁保护。
内容的提问来源于stack exchange,提问作者Abraham
相关产品推荐
相关产品推荐

