一、线程基础:threading 模块核心元素

1. threading.Thread 类(创建线程的核心)

用于创建和管理线程,初始化时的参数及作用如下:

参数 作用说明 默认值
group 预留参数,用于线程组管理(目前未实现,无需设置) None
target 线程要执行的目标函数(可调用对象,如函数、方法) None
name 线程名称(便于调试识别,不设置则自动生成如 Thread-1 自动分配
args 传递给 target 函数的位置参数(元组形式,如 (1, 2) ()
kwargs 传递给 target 函数的关键字参数(字典形式,如 {'a': 1} {}
daemon 是否为守护线程(True 时,主线程结束则强制终止;False 则需等待完成) 继承父线程
2. 线程常用方法与属性
方法 / 属性 作用说明
start() 启动线程(让线程进入就绪状态,等待 CPU 调度执行 target 函数)
join(timeout=None) 阻塞当前线程(通常是主线程),等待被调用线程执行完毕(timeout 为超时秒数)
is_alive() 返回 True 表示线程已启动且未结束(存活状态)
name / getName() 获取线程名称(name 是属性,getName() 是方法,效果相同)
setName(name) 设置线程名称(也可直接赋值 thread.name = "新名称"
daemon / isDaemon() 获取是否为守护线程(daemon 是属性,isDaemon() 是方法)
setDaemon(daemon) 设置是否为守护线程(也可直接赋值 thread.daemon = True
3. threading.current_thread() 函数
  • 作用:返回当前正在执行的线程对象。
  • 用途:在多线程中获取当前线程的信息(如名称、是否为守护线程等),常用于日志输出或线程身份判断。

二、案例演示:从基础到进阶

案例 1:基础线程创建(参数 targetnameargs 与方法 start()join()

目标:演示如何用基本参数创建线程,以及 start()(启动)和 join()(等待)的作用。

import threading
import time

# 定义线程要执行的目标函数(带参数)
def print_info(num, text):
    # 用 current_thread() 获取当前线程对象,打印线程名称
    print(f"当前线程:{threading.current_thread().name},参数:num={num}, text={text}")
    time.sleep(2)  # 模拟任务耗时

# 1. 创建线程:指定 target、name、args
# - target:要执行的函数 print_info
# - name:线程名称(便于识别)
# - args:传递给 print_info 的位置参数(元组形式)
thread1 = threading.Thread(
    target=print_info,
    name="线程A",
    args=(1, "Hello")  # 对应 print_info 的 num=1, text="Hello"
)

thread2 = threading.Thread(
    target=print_info,
    name="线程B",
    args=(2, "World")
)

# 2. 启动线程:调用 start() 后,线程进入就绪状态,等待 CPU 调度
print("启动线程A和线程B...")
thread1.start()
thread2.start()

# 3. 等待线程完成:主线程调用 join() 后会阻塞,直到线程执行完毕
print("主线程等待线程A和线程B结束...")
thread1.join()  # 等待线程A完成
thread2.join()  # 等待线程B完成

print("所有线程执行完毕,主线程结束")
    

执行详解

  • thread1.start() 和 thread2.start() 启动线程后,CPU 会调度它们交替执行(宏观上并行)。
  • threading.current_thread().name 在 print_info 中获取当前执行线程的名称(“线程 A” 或 “线程 B”)。
  • join() 确保主线程等待子线程完成后再结束,避免主线程提前退出导致子线程被中断。

输出结果

启动线程A和线程B...
主线程等待线程A和线程B结束...
当前线程:线程A,参数:num=1, text=Hello
当前线程:线程B,参数:num=2, text=World
(等待2秒)
所有线程执行完毕,主线程结束
案例 2:守护线程(daemon 属性)与 is_alive() 方法

目标:演示守护线程的特性(随主线程结束而终止)和 is_alive() 方法(判断线程是否存活)。

import threading
import time

# 守护线程任务:模拟后台监控(无限循环)
def background_monitor():
    while True:
        # 获取当前线程信息(是否为守护线程)
        current = threading.current_thread()
        print(f"[{current.name}] 后台监控中(守护线程:{current.daemon})")
        time.sleep(1)  # 每秒打印一次

# 非守护线程任务:模拟主任务(执行3秒后结束)
def main_task():
    print(f"[{threading.current_thread().name}] 主任务开始")
    time.sleep(3)
    print(f"[{threading.current_thread().name}] 主任务结束")

# 1. 创建守护线程
monitor_thread = threading.Thread(
    target=background_monitor,
    name="监控线程"
)
monitor_thread.daemon = True  # 设置为守护线程

# 2. 创建非守护线程
main_thread = threading.Thread(
    target=main_task,
    name="主任务线程"
)
# 非守护线程默认 daemon=False,无需额外设置

# 3. 启动线程
monitor_thread.start()
main_thread.start()

# 4. 用 is_alive() 监控线程状态(每隔1秒检查一次)
for i in range(4):
    print(f"\n第 {i+1} 秒线程状态:")
    print(f"监控线程是否存活:{monitor_thread.is_alive()}")
    print(f"主任务线程是否存活:{main_thread.is_alive()}")
    time.sleep(1)

# 5. 等待非守护线程结束(守护线程无需等待)
main_thread.join()
print("\n主线程:所有非守护线程已结束,准备退出(守护线程将被终止)")
    

执行详解

  • 守护线程 monitor_thread 因 daemon=True,当所有非守护线程(main_thread 和主线程)结束时,会被强制终止(即使处于无限循环)。
  • is_alive() 用于判断线程是否存活:前 3 秒内,main_thread 存活;第 4 秒时,main_thread 已结束,is_alive() 返回 False

输出结果

[监控线程] 后台监控中(守护线程:True)
[主任务线程] 主任务开始

第 1 秒线程状态:
监控线程是否存活:True
主任务线程是否存活:True
[监控线程] 后台监控中(守护线程:True)

第 2 秒线程状态:
监控线程是否存活:True
主任务线程是否存活:True
[监控线程] 后台监控中(守护线程:True)

第 3 秒线程状态:
监控线程是否存活:True
主任务线程是否存活:True
[主任务线程] 主任务结束
[监控线程] 后台监控中(守护线程:True)

第 4 秒线程状态:
监控线程是否存活:True
主任务线程是否存活:False

主线程:所有非守护线程已结束,准备退出(守护线程将被终止)
案例 3:关键字参数 kwargs 与线程命名方法

目标:演示 kwargs 传递关键字参数,以及 setName() 方法(或 name 属性)设置线程名称。

import threading
import time

# 守护线程任务:模拟后台监控(无限循环)
def background_monitor():
    while True:
        # 获取当前线程信息(是否为守护线程)
        current = threading.current_thread()
        print(f"[{current.name}] 后台监控中(守护线程:{current.daemon})")
        time.sleep(1)  # 每秒打印一次

# 非守护线程任务:模拟主任务(执行3秒后结束)
def main_task():
    print(f"[{threading.current_thread().name}] 主任务开始")
    time.sleep(3)
    print(f"[{threading.current_thread().name}] 主任务结束")

# 1. 创建守护线程
monitor_thread = threading.Thread(
    target=background_monitor,
    name="监控线程"
)
monitor_thread.daemon = True  # 设置为守护线程

# 2. 创建非守护线程
main_thread = threading.Thread(
    target=main_task,
    name="主任务线程"
)
# 非守护线程默认 daemon=False,无需额外设置

# 3. 启动线程
monitor_thread.start()
main_thread.start()

# 4. 用 is_alive() 监控线程状态(每隔1秒检查一次)
for i in range(4):
    print(f"\n第 {i+1} 秒线程状态:")
    print(f"监控线程是否存活:{monitor_thread.is_alive()}")
    print(f"主任务线程是否存活:{main_thread.is_alive()}")
    time.sleep(1)

# 5. 等待非守护线程结束(守护线程无需等待)
main_thread.join()
print("\n主线程:所有非守护线程已结束,准备退出(守护线程将被终止)")
    

小结

  1. 线程创建参数

    • target 是线程执行的核心函数,args 和 kwargs 用于传递参数。
    • name 用于标识线程,daemon 控制线程是否随主线程终止。
  2. 关键方法

    • start():唯一启动线程的方法(不可重复调用)。
    • join():确保主线程等待子线程完成,避免异步执行导致的逻辑错误。
    • is_alive():判断线程状态,常用于监控或条件判断(如 “线程存活时不重复启动”)。
  3. threading.current_thread()
    在函数内部获取当前执行的线程对象,可用于获取线程名称、判断是否为守护线程等,是多线程调试的重要工具。

        通过这些知识点,可灵活实现多线程并发任务,尤其适合 I/O 密集型场景(如网络请求、文件读写)。对于 CPU 密集型任务,需结合多进程(multiprocessing)避开 GIL 限制。

三、主线程与子线程

        线程是 进程内的执行单元,一个进程至少包含一个线程(主线程)。主线程是程序的 “入口”,非主线程(常称 “子线程”)是手动创建的并行执行单元,二者共享进程的内存空间(如全局变量、堆内存)。

1. 核心定义

类型 定义与角色
主线程 程序启动时,操作系统(OS)自动创建的默认线程,是程序的 “总控制器”:
1. 负责执行程序的入口代码(如 if __name__ == "__main__": 块);
2. 可手动创建其他子线程;
3. 默认名:MainThread(可通过 thread.name 查看)。
非主线程 由主线程或其他子线程通过 threading.Thread 手动创建的线程,用于 “并行” 执行耗时任务(如 IO 操作),避免阻塞主线程。
默认名:Thread-1Thread-2...(可自定义)。

2. 关键特点

  • 资源共享:同一进程内的所有线程共享进程的内存空间(全局变量、函数、类实例等),无需额外通信机制即可访问(但需注意线程安全,如用 threading.Lock 避免竞争)。
  • 执行调度:线程由 OS 调度器统一管理,执行顺序是 “抢占式” 的(无法预测哪个线程先执行),取决于 OS 的调度策略。
  • 生命周期关联
    • 非守护线程(默认):主线程会等待所有非守护线程执行完毕后才结束(即使主线程代码已执行完);
    • 守护线程(daemon=True):主线程结束时,守护线程会被强制终止(无论是否执行完),常用于 “辅助任务”(如日志打印)。

3. 核心方法与属性(必掌握)

方法 / 属性 作用说明
threading.current_thread() 获取当前正在执行的线程对象,常用于区分主线程与子线程。
threading.Thread(target, args, kwargs, name, daemon) 创建子线程的核心类:
target:线程要执行的函数(必填);
args:传给 target 的位置参数(元组,空元组需写 ());
kwargs:传给 target 的关键字参数(字典);
name:自定义线程名(字符串,可选);
daemon:是否设为守护线程(布尔值,可选,默认 False)。
thread.start() 启动线程:OS 会调用线程的 run() 方法(不可直接调用 run(),否则会变成主线程内的普通函数执行)。
thread.join(timeout=None) 主线程阻塞等待该子线程执行完毕(可选 timeout:超时时间,单位秒);若不调用 join(),主线程可能先于子线程结束。
thread.name 获取 / 设置线程名(如 current_thread().name 查看当前线程名)。
thread.is_alive() 判断线程是否仍在执行(返回布尔值)。

4. 代码案例(带详细注释)

案例 1:区分主线程与子线程,观察执行顺序
import threading
import time

# 定义子线程要执行的函数
def task(task_name, sleep_time):
    # 获取当前执行该函数的线程对象
    current_thread = threading.current_thread()
    print(f"【{current_thread.name}】开始执行任务:{task_name}")
    time.sleep(sleep_time)  # 模拟耗时操作(IO/计算)
    print(f"【{current_thread.name}】完成任务:{task_name}")

if __name__ == "__main__":
    # 1. 查看主线程信息
    main_thread = threading.current_thread()
    print(f"=== 主线程启动:{main_thread.name},线程ID:{main_thread.ident} ===")

    # 2. 创建2个子线程(非守护线程,默认)
    # 子线程1:用 args 传位置参数(元组,注意逗号)
    thread1 = threading.Thread(
        target=task,
        args=("下载文件", 2),  # 对应 task 的 (task_name, sleep_time)
        name="FileDownloadThread"  # 自定义线程名
    )

    # 子线程2:用 kwargs 传关键字参数(字典)
    thread2 = threading.Thread(
        target=task,
        kwargs={"task_name": "处理数据", "sleep_time": 1},
        name="DataProcessThread"
    )

    # 3. 启动子线程(OS 开始调度)
    thread1.start()
    thread2.start()

    # 4. 主线程等待子线程执行完毕(若注释 join(),主线程会先打印“主线程结束”)
    thread1.join()  # 主线程阻塞,直到 thread1 完成
    thread2.join()  # 主线程阻塞,直到 thread2 完成

    # 5. 主线程执行收尾工作
    print(f"=== 主线程 {main_thread.name} 结束 ===")

执行结果(顺序可能因 OS 调度变化)

=== 主线程启动:MainThread,线程ID:140703324872512 ===
【FileDownloadThread】开始执行任务:下载文件
【DataProcessThread】开始执行任务:处理数据
【DataProcessThread】完成任务:处理数据  # 1秒后完成
【FileDownloadThread】完成任务:下载文件  # 2秒后完成
=== 主线程 MainThread 结束 ===

关键说明

  • 通过 threading.current_thread() 明确了 “哪个线程在执行任务”;
  • join() 确保主线程等待子线程完成后再结束,若删除 join(),主线程会直接打印 “结束”,但子线程仍会继续执行(因是非守护线程)。

四、父进程与子进程

        进程是 OS 分配资源的基本单位(如内存、CPU),一个程序运行后至少对应一个进程(父进程)。父进程通过 multiprocessing 等模块创建子进程,二者拥有完全独立的内存空间(资源不共享)。

1. 核心定义

类型 定义与角色
父进程 启动后创建其他进程的 “初始进程”:
1. 如运行 python script.py 时,OS 创建的进程就是父进程;
2. 父进程可通过 multiprocessing.Process 创建子进程;
3. 每个进程有唯一的 PID(进程 ID),父进程的 PID 称为 PPID(父进程 ID)。
子进程 由父进程通过 OS 系统调用(如 Unix 的 fork()、Windows 的 CreateProcess())创建的新进程:
1. 子进程会复制父进程的代码段、数据段(但后续修改不影响父进程,即 “写时复制”);
2. 子进程有独立的内存空间,全局变量、栈内存与父进程完全隔离。

2. 关键特点

  • 资源隔离:父子进程的内存空间完全独立(全局变量、函数参数等不共享),子进程修改全局变量不会影响父进程(与线程的核心区别)。
  • 创建开销大:进程创建需要 OS 分配新的内存、CPU 资源,开销远大于线程(线程共享进程资源,创建快)。
  • 生命周期关联
    • 子进程默认不随父进程结束而结束(父进程结束后,子进程会成为 “孤儿进程”,由 OS 的 “init 进程” 或 “systemd” 收养);
    • 父进程若未回收子进程资源(如 exit code),子进程会成为 “僵尸进程”(占用 PID 等资源)。
  • 通信需 IPC 机制:父子进程需通过 跨进程通信(IPC) 交换数据,如 Queue(队列)、Pipe(管道)、共享内存、Socket 等(multiprocessing 封装了这些机制)。

3. 核心方法与属性(必掌握)

方法 / 属性 作用说明
multiprocessing.Process(target, args, kwargs, name) 创建子进程的核心类(用法类似 threading.Thread):
target:子进程要执行的函数;
args/kwargs:传给函数的参数(同线程);
name:自定义进程名。
process.start() 启动子进程:OS 会创建新进程并执行 target 函数(不可直接调用 run())。
process.join(timeout=None) 父进程阻塞等待子进程执行完毕(避免子进程成为僵尸进程)。
process.pid 获取子进程的 PID(进程 ID,唯一标识)。
os.getppid() 在子进程中调用,获取父进程的 PID(需导入 os 模块)。
multiprocessing.Queue() 跨进程安全的队列,用于父子进程通信(put() 存数据,get() 取数据)。
process.terminate() 强制终止子进程(类似 OS 的 kill 命令,需谨慎使用)。

4. 代码案例(带详细注释)

案例 1:区分父进程与子进程,查看 PID/PPID
import multiprocessing
import os
import time

# 定义子进程要执行的函数
def child_task(task_name):
    # 子进程中获取自身 PID 和父进程 PPID
    child_pid = os.getpid()
    parent_pid = os.getppid()
    print(f"【子进程】名称:{multiprocessing.current_process().name}")
    print(f"【子进程】PID:{child_pid},父进程 PPID:{parent_pid}")
    print(f"【子进程】执行任务:{task_name},耗时2秒...")
    time.sleep(2)
    print(f"【子进程】任务 {task_name} 完成!")

if __name__ == "__main__":
    # 1. 父进程信息
    parent_process = multiprocessing.current_process()
    parent_pid = os.getpid()
    print(f"=== 父进程启动 ===")
    print(f"【父进程】名称:{parent_process.name},PID:{parent_pid}")

    # 2. 创建子进程(注意:Windows 下必须放在 if __name__ == "__main__": 内,避免递归创建)
    child_process = multiprocessing.Process(
        target=child_task,
        args=("处理大数据",),  # 位置参数(元组)
        name="BigDataProcess"  # 自定义子进程名
    )

    # 3. 启动子进程
    child_process.start()

    # 4. 父进程等待子进程完成(避免子进程成为僵尸进程)
    child_process.join()
    print(f"=== 父进程 PID:{parent_pid} 结束 ===")

执行结果

=== 父进程启动 ===
【父进程】名称:MainProcess,PID:1234
【子进程】名称:BigDataProcess
【子进程】PID:5678,父进程 PPID:1234
【子进程】执行任务:处理大数据,耗时2秒...
【子进程】任务 处理大数据 完成!
=== 父进程 PID:1234 结束 ===

关键说明

  • 子进程的 PPID 等于父进程的 PID,证明父子关系;
  • Windows 下必须将 Process 创建放在 if __name__ == "__main__": 内:因为 Windows 没有 fork(),会通过 “导入模块” 创建子进程,若不限制会递归创建进程。
案例 2:验证进程资源隔离 + 用 Queue 实现通信
import multiprocessing
import time

# 全局变量(验证隔离性)
global_var = "父进程初始值"

def child_task(queue):
    """子进程任务:修改全局变量 + 向队列传数据"""
    global global_var  # 声明使用全局变量
    print(f"【子进程】初始 global_var:{global_var}")  # 复制父进程的初始值
    global_var = "子进程修改后的值"  # 修改全局变量
    print(f"【子进程】修改后 global_var:{global_var}")

    # 向队列传递数据(子进程 → 父进程)
    queue.put(f"子进程传递的数据:当前时间 {time.ctime()}")
    time.sleep(1)

if __name__ == "__main__":
    # 1. 创建跨进程队列(用于父子通信)
    comm_queue = multiprocessing.Queue()

    # 2. 创建子进程
    child_process = multiprocessing.Process(
        target=child_task,
        args=(comm_queue,)  # 队列作为参数传递(需可序列化)
    )

    # 3. 启动子进程
    child_process.start()
    child_process.join()

    # 4. 父进程查看全局变量(验证隔离性)
    print(f"\n【父进程】全局变量 global_var:{global_var}")  # 仍为初始值,未被修改

    # 5. 父进程从队列获取子进程数据(通信)
    received_data = comm_queue.get()  # 阻塞等待队列数据
    print(f"【父进程】收到子进程数据:{received_data}")

执行结果

【子进程】初始 global_var:父进程初始值
【子进程】修改后 global_var:子进程修改后的值

【父进程】全局变量 global_var:父进程初始值  # 资源隔离,子进程修改不影响父进程
【父进程】收到子进程数据:子进程传递的数据:当前时间 Wed Sep  3 10:00:00 2025

关键说明

  • 资源隔离:子进程修改的 global_var 是其独立内存中的副本,父进程的 global_var 完全不受影响(与线程共享全局变量形成鲜明对比);
  • 跨进程通信multiprocessing.Queue 是线程安全、进程安全的,通过序列化(pickle)实现数据传递,支持父子进程双向通信。

小结

  1. 主线程是 “入口”,子线程是 “并行单元”:共享内存,适合 IO 密集型任务,需注意线程安全;
  2. 父进程是 “创建者”,子进程是 “独立个体”:内存隔离,适合 CPU 密集型任务,需通过 IPC 通信;
  3. 核心选择依据:IO 密集用线程(高效),CPU 密集用进程(利用多核),复杂场景可结合(如多进程 + 多线程)。

五、锁机制

        在多线程 / 多进程编程中,当多个执行单元(线程 / 进程)同时操作共享资源(如全局变量、文件、数据库)时,可能会出现竞争条件(Race Condition)—— 即资源状态因并发操作而变得不一致。锁机制是解决这一问题的核心手段,它通过 “互斥访问” 确保同一时间只有一个执行单元能操作共享资源。

线程锁(threading.Lock):解决线程间资源竞争

        线程共享进程的内存空间,全局变量、函数参数等是典型的共享资源。线程锁用于保证同一时间只有一个线程能执行 “临界区代码”(操作共享资源的代码段)。

1. 线程锁的核心方法
方法 作用说明
lock = threading.Lock() 创建一个线程锁对象(初始为 “未锁定” 状态)。
lock.acquire(blocking=True, timeout=-1) 尝试获取锁:
- 若锁未被占用,当前线程获取锁(锁变为 “锁定” 状态),继续执行;
- 若锁已被占用,blocking=True 时阻塞等待(默认),timeout 设超时时间(秒);
- 成功获取返回 True,超时返回 False
lock.release() 释放锁(锁变为 “未锁定” 状态),必须由持有锁的线程调用,否则报错。
with lock: 上下文管理器语法(推荐):自动获取锁,代码块执行完后自动释放(即使报错也会释放,避免死锁)。
2. 案例:无锁导致的混乱 vs 有锁的正确同步

场景:3 个线程同时给一个全局变量(余额)加 1,各加 1000 次,预期结果是 3000。

import threading
import time

# 共享资源:全局变量(余额)
balance = 0

def add_money(lock=None):
    global balance
    for _ in range(1000):
        # 临界区:操作共享资源的代码
        if lock:
            # 方案2:有锁(用with自动获取和释放锁)
            with lock:
                current = balance
                time.sleep(0.0001)  # 模拟耗时操作(放大竞争问题)
                balance = current + 1
        else:
            # 方案1:无锁(会出现竞争条件)
            current = balance
            time.sleep(0.0001)  # 模拟耗时操作(放大竞争问题)
            balance = current + 1

# 测试无锁情况
balance = 0
threads = []
for _ in range(3):
    t = threading.Thread(target=add_money)
    threads.append(t)
    t.start()
for t in threads:
    t.join()
print(f"无锁时的结果:{balance}(预期3000,实际可能小于3000)")

# 测试有锁情况
balance = 0
lock = threading.Lock()  # 创建线程锁
threads = []
for _ in range(3):
    # 将锁传给线程函数
    t = threading.Thread(target=add_money, args=(lock,))
    threads.append(t)
    t.start()
for t in threads:
    t.join()
print(f"有锁时的结果:{balance}(预期3000,实际正确)")
    

执行结果分析

  • 无锁情况:多个线程同时读取 balance,修改后写回,导致 “覆盖”(如线程 1 和线程 2 同时读 balance=100,都加 1 后写回 101,实际应是 102),最终结果小于 3000。
  • 有锁情况with lock 确保同一时间只有一个线程执行 current = balance 到 balance = current + 1 的逻辑,避免覆盖,结果正确为 3000。

进程锁(multiprocessing.Lock):解决进程间资源竞争

        进程内存隔离,但可能共享外部资源(如文件、数据库、打印机)。进程锁用于保证同一时间只有一个进程能操作这些共享资源。

1. 进程锁的核心方法

与线程锁完全一致(接口设计统一):

  • lock = multiprocessing.Lock() 创建进程锁;
  • acquire()/release() 或 with lock: 控制资源访问。
2. 案例:多进程写入同一文件的同步问题

场景:3 个进程同时向一个文件写入内容,无锁会导致内容混乱,有锁则保证顺序写入。

import multiprocessing
import time

def write_file(process_id, lock):
    # 整个循环放入with块:持有锁直到写完所有3行
    with lock:
        for i in range(3):
            content = f"进程{process_id}的第{i+1}行内容\n"
            with open("sequential_file.txt", "a", encoding="utf-8") as f:
                f.write(content)
                time.sleep(0.1)  # 模拟耗时,此时仍持有锁

# 清空文件
with open("sequential_file.txt", "w", encoding="utf-8") as f:
    pass

lock = multiprocessing.Lock()
processes = [
    multiprocessing.Process(target=write_file, args=(0, lock)),
    multiprocessing.Process(target=write_file, args=(1, lock)),
    multiprocessing.Process(target=write_file, args=(2, lock))
]

for p in processes:
    p.start()
for p in processes:
    p.join()

print("写入完成,文件内容按进程顺序排列")
    
输出结果(严格按进程顺序):
进程0的第1行内容
进程0的第2行内容
进程0的第3行内容
进程1的第1行内容
进程1的第2行内容
进程1的第3行内容
进程2的第1行内容
进程2的第2行内容
进程2的第3行内容

原因:进程 0 获取锁后,会持有锁直到写完 3 行才释放;之后进程 1 可能获取锁写 3 行,最后进程 2 写 3 行。但这种方式并发效率低(相当于串行执行),只适合必须严格顺序的场景。

死锁问题与避免

        死锁是锁机制的常见陷阱:当多个执行单元(线程 / 进程)互相等待对方释放锁时,程序会陷入无限阻塞。

1. 死锁示例(多线程场景)
import threading

# 创建两个锁
lock1 = threading.Lock()
lock2 = threading.Lock()

def task1():
    with lock1:  # 获取lock1
        print("任务1获取了lock1,等待lock2...")
        with lock2:  # 尝试获取lock2,但此时可能被task2持有
            print("任务1获取了lock2,执行完成")

def task2():
    with lock2:  # 获取lock2
        print("任务2获取了lock2,等待lock1...")
        with lock1:  # 尝试获取lock1,但此时可能被task1持有
            print("任务2获取了lock1,执行完成")

t1 = threading.Thread(target=task1)
t2 = threading.Thread(target=task2)
t1.start()
t2.start()
t1.join()
t2.join()

执行结果:程序会卡住,输出:

任务1获取了lock1,等待lock2...
任务2获取了lock2,等待lock1...
2. 死锁避免策略
  • 按顺序获取锁:所有执行单元严格按同一顺序获取多个锁(如先获取 lock1 再获取 lock2)。
  • 设置超时时间:用 lock.acquire(timeout=5) 避免无限等待,超时后释放已获取的锁。
  • 使用可重入锁(RLock:同一线程可多次获取同一把锁(threading.RLock),避免自己阻塞自己。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐