Python 3.6.4多线程下仅读取共享列表长度是否需加锁?
回答:是的,获取列表长度时建议加锁(或根据具体场景判断)
首先直接给结论:如果你的代码需要保证长度的准确性,或者后续要基于这个长度做关联操作,那必须加锁;如果只是要一个近似值,不加锁也能运行,但绝对不推荐。
接下来拆解原因:
- 先澄清你提到的文件读取类比:文件读取之所以不用锁,是因为大多数文件系统会给读操作提供一致性保障(比如读取的是某个时刻的文件快照),但内存中的列表完全不一样——它是个可变对象,所有操作都是直接在内存上修改,没有快照机制。
- 再说说Python里
len(list)的本质:在CPython(包括你用的3.6.4版本)中,len()读取列表长度是个原子操作——因为列表内部维护了一个长度计数器,len()直接读这个计数器,而这个读取动作在GIL的保护下不会被其他线程打断。这意味着你不会读到一个“半修改”的长度(比如既不是修改前也不是修改后的中间值)。
但重点来了:原子性不代表一致性,举两个典型场景你就能明白:
- 场景1:如果你的增删操作是复合操作(比如先删除一个元素,再添加一个新元素,这两步都在同一个锁里执行),那如果读长度不加锁,可能刚好读到删除后、添加前的长度,这显然不是你想要的最终状态。
- 场景2:即使是单个原子修改(比如
append()),如果你读完长度后要做其他操作(比如“因为长度是5,所以去访问list[4]”),那不加锁的话,在你读完长度到访问元素的间隙,列表可能已经被其他线程修改,导致索引越界或者访问到错误的元素。
而你已经给增删操作加了锁,那读长度时复用同一个锁就可以保证:你读到的长度一定是某个完整增删操作完成后的状态,不会出现中间值,也能避免后续操作的不一致。
最后给个简单的示例代码:
import threading lock = threading.Lock() shared_list = [] def add_item(item): with lock: shared_list.append(item) def remove_item(): with lock: if shared_list: shared_list.pop() def get_list_length(): with lock: # 这里加锁,保证拿到的是准确的最新长度 return len(shared_list)
内容的提问来源于stack exchange,提问作者Nisba
相关产品推荐
相关产品推荐

