位置:首页 > Python > Ubuntu 上 Python 多线程编程怎么写:从 threading、Lock 到 GIL 一次讲清

Ubuntu 上 Python 多线程编程怎么写:从 threading、Lock 到 GIL 一次讲清

时间:2026-08-24  |  作者:电竞小硕  |  阅读:0

目录

  1. 用 threading 创建并启动线程
  2. 多个线程共享数据时,为什么要加锁
  3. GIL 为什么会影响 Python 多线程性能
  4. 写在最后:先判断任务类型,再决定并发方式

前言

在 Ubuntu 上写 Python 多线程,很多人先学会了 threading.Thread(),却很快遇到几个更实际的问题:线程为什么会乱序输出、共享数据为什么容易出错、CPU 任务为什么开了线程也不见得更快。下面就按“先跑起来、再保证正确、最后判断是否值得用”的顺序,把 threadingLock 和 GIL 的关键点拆开说明。

在 Ubuntu 上做 Python 并发,很多人第一步都会想到多线程,但真正上手时,常见问题通常不是“线程怎么开”,而是“输出为什么乱了”“共享数据为什么会出错”以及“为什么开了线程性能还是没上去”。这篇文章按实际使用顺序,把 threading 的基本写法、加锁方法和 GIL 带来的限制串起来讲清楚,让你能在写代码前先判断这是不是合适的方案。

用 threading 创建并启动线程

在 Ubuntu 上写 Python 多线程,标准库里的 threading 模块通常就是首选。最基础的用法包括三步:定义线程要执行的函数、创建 Thread 对象、调用 start() 启动线程。

展示 Python 多线程基础执行流程的白底信息图,包含函数定义、创建两个线程、start 启动、join 等待和输出交错现象。
Python 线程创建与执行流程用一张流程图看清 Python 多线程示例里从函数到线程结束的关键步骤。
import threading
# 定义一个线程要执行的函数
def print_numbers():
    for i in range(5):
        print(f"Number from thread: {i}")

# 创建线程对象
thread1 = threading.Thread(target=print_numbers)
thread2 = threading.Thread(target=print_numbers)

# 启动线程
thread1.start()
thread2.start()

# 等待所有线程完成
thread1.join()
thread2.join()

print("Finished executing threads")

这段代码里,print_numbers 会循环打印 5 次数字;随后创建两个线程,它们都执行同一个目标函数。调用 start() 后,线程进入运行状态;调用 join() 则表示主线程会等待它们执行完毕,再继续往下走。

start() 和 join() 分别做什么

start() 的作用是正式启动线程,真正让线程去执行目标函数;join() 则负责阻塞当前线程,直到对应线程结束。对入门代码来说,这两个调用几乎总是成对出现,否则主线程可能比子线程更早退出,导致结果不完整。

为什么输出顺序可能会交错

需要注意的是,多线程并发执行时,两个线程可能同时访问标准输出,因此打印结果不一定按你肉眼预期的顺序排列。也就是说,代码逻辑没错,输出看起来“乱序”并不一定是程序异常,而是并发执行的正常表现。

多个线程共享数据时,为什么要加锁

当多个线程只是在各自处理任务时,问题通常还不明显;一旦它们开始共享同一份数据或访问同一个资源,就可能出现竞态条件。为了避免线程互相打断,Python 提供了 Lock 这样的同步工具。

展示 Lock 如何保护临界区的白底信息图,包含两个线程竞争同一输出资源、with lock 互斥进入和减少输出混乱的效果。
Lock 如何避免线程竞争共享资源一旦被多个线程同时访问,就需要明确临界区;这张图适合说明 Lock 在示例中的作用范围。
import threading

# 创建一个锁对象
lock = threading.Lock()

# 定义一个线程要执行的函数
def print_numbers_with_lock(thread_name):
    for i in range(5):
        # 获取锁,确保同一时间只有一个线程可以执行这段代码
        with lock:
            print(f"Number from {thread_name}: {i}")

# 创建线程对象
thread1 = threading.Thread(target=print_numbers_with_lock, args=("Thread-1",))
thread2 = threading.Thread(target=print_numbers_with_lock, args=("Thread-2",))

# 启动线程
thread1.start()
thread2.start()

# 等待所有线程完成
thread1.join()
thread2.join()

print("Finished executing threads")

这里先创建了一个 threading.Lock() 对象,然后在关键代码外层使用 with lock。这样一来,同一时刻只会有一个线程进入这段打印逻辑,另一个线程需要等锁释放后才能继续执行。

with lock 的实际效果

with lock 的意义不只是“写法更简洁”,更重要的是它明确划定了临界区。凡是放进这段代码块中的操作,都会在互斥条件下执行,适合保护共享变量、文件写入、日志输出等容易互相影响的资源。

加锁能解决什么,不能解决什么

加锁可以减少多个线程同时操作同一资源导致的数据混乱,但它并不会让程序天然变快。锁本身会带来等待成本,如果临界区写得过大,线程之间反而会排队严重,程序表现可能更接近串行执行。

GIL 为什么会影响 Python 多线程性能

除了线程同步,Python 多线程还有一个绕不开的话题,就是 GIL,也就是全局解释器锁。它的核心限制是:同一时刻只能有一个线程执行 Python 字节码。

展示 GIL 对 Python 多线程适用场景影响的白底信息图,对比 I/O 密集型任务与 CPU 密集型任务,并指出 multiprocessing 的替代方向。
GIL 与并发方案选择不是所有并发任务都适合线程,这张图用场景对比解释 GIL 为什么会影响性能判断。

这意味着,多线程并不等于多核并行。对于 I/O 密集型任务,例如网络请求、文件读写、等待外部响应,多线程依然很有用,因为线程可以在等待期间切换执行;但如果是 CPU 密集型任务,多线程往往不能带来预期中的性能提升。

什么时候适合 threading,什么时候该换方案

如果你的任务主要时间花在等待上,比如下载、接口调用、磁盘操作,那么 threading 往往足够直接;如果任务主要是在做大量计算,那么就要谨慎看待多线程收益。这种情况下,更常见的替代方案是 multiprocessing,用多进程来实现真正的并行计算。

写在最后:先判断任务类型,再决定并发方式

把这几个知识点连起来看,Ubuntu 上的 Python 多线程并不复杂:会创建线程、会用 start()join() 控制执行流程,只是第一步;真正决定代码是否稳定、是否有效的,是你有没有处理共享资源,以及有没有正确理解 GIL 的限制。

如果只是想快速把并发任务跑起来,threading 已经足够实用;如果你追求的是 CPU 计算性能,就应该尽早评估 multiprocessing 是否更合适。

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多