You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何编写可靠示例演示多线程中Lock解决的线程不安全问题?

如何修改代码复现线程不安全问题并演示Lock的必要性

原代码没出现交错的原因

你的代码里用了list.append(),但在CPython中这个操作是原子性的(受全局解释器锁GIL保护),单个append不会被其他线程打断。再加上线程调度的特性,某个线程可能连续执行完几百次append才会被切换,所以最终输出是连续的同值元素,不会出现预期的交错。

要复现线程不安全,得构造非原子的共享数据操作——也就是把一个操作拆成多个步骤,中间可以被其他线程打断。

修改后的无锁示例(可稳定复现问题)

把append替换成"先获取列表长度,再通过索引赋值"的逻辑,这个过程是分步的,中间会被线程切换打断,导致数据覆盖或丢失:

import threading

shared_list = []

def simulate_unsafe_append(value):
    for _ in range(1000):
        # 非原子操作:先读长度,再赋值
        current_len = len(shared_list)
        # 可选:加微小延迟放大线程切换概率,更容易复现问题
        # threading.sleep(0.0001)
        shared_list.insert(current_len, value)  # 手动模拟append逻辑

threads = []
for i in range(10):
    thread = threading.Thread(target=simulate_unsafe_append, args=(i,))
    threads.append(thread)
    thread.start()

for thread in threads:
    thread.join()

print(f"实际列表长度: {len(shared_list)}")
print(f"预期列表长度: {10 * 1000}")
# 查看前20个元素,会发现大量重复值或缺失值
print(shared_list[:20])

运行这段代码,你会发现实际列表长度远小于预期的10000,而且列表里会出现大量重复元素——这就是线程不安全导致的:多个线程同时读取到相同的current_len,然后往同一个位置插入元素,后面的插入会覆盖前面的,最终造成元素丢失。

如果觉得问题不够明显,可以把注释里的threading.sleep(0.0001)打开,强制让线程在两步之间切换,问题会更突出。

加上Lock的正确示例

现在给共享操作加上threading.Lock(),保证同一时间只有一个线程执行非原子操作:

import threading

shared_list = []
lock = threading.Lock()  # 创建锁对象

def safe_append(value):
    for _ in range(1000):
        with lock:  # 使用with自动获取/释放锁,避免手动操作遗漏
            current_len = len(shared_list)
            shared_list.insert(current_len, value)

threads = []
for i in range(10):
    thread = threading.Thread(target=safe_append, args=(i,))
    threads.append(thread)
    thread.start()

for thread in threads:
    thread.join()

print(f"实际列表长度: {len(shared_list)}")
print(f"预期列表长度: {10 * 1000}")
print(shared_list[:20])

加上锁之后,每次只有一个线程能执行current_len = len(shared_list)和shared_list.insert(...)这两步,不会出现多个线程同时操作的情况,最终列表长度刚好是10000,元素也会出现交错(因为线程会在锁释放后切换)。

补充说明

如果你还是想保留append的形式来演示,可以把append和其他操作结合,比如先读取列表最后一个元素再append,这样整个过程是非原子的:

import threading

shared_list = []

def unsafe_operation(value):
    for _ in range(1000):
        # 非原子操作:读最后一个元素 + append
        if shared_list:
            last = shared_list[-1]
        shared_list.append(value)

# 后续线程创建和join逻辑和之前一致

不过这种方式的问题不如手动实现append明显,还是推荐用前面的"读长度+插入"的方式,能稳定复现数据丢失的问题,更直观说明Lock的必要性。

内容的提问来源于stack exchange,提问作者memelord23

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 17:35:08