杰瑞科技汇

python安装therading

threading 模块是 Python 的标准库,所以你不需要“安装”它,只要你安装了 Python,就已经拥有了 threading 模块。

你只需要在代码中 import 它即可使用。

import threading

什么是 threading?为什么需要它?

threading 模块允许你在一个程序中创建和管理多个线程。线程是程序执行流的最小单位

想象一下你正在厨房做饭:

  • 单线程程序:你只有一个你,你必须先淘米(任务A),然后等米饭煮上,再切菜(任务B),切菜的时候,你就不能去管米饭了,这就是“顺序执行”,效率不高。
  • 多线程程序:你变成了你和你自己(两个线程),一个你负责淘米并看着它煮(任务A),另一个你利用煮饭的空闲时间去切菜(任务B),虽然你还是在同一个厨房(同一个进程)里,但两个任务可以“进行,大大提高了效率。

使用 threading 的主要目的就是实现“并发”(Concurrency),让程序看起来像是“执行多个任务,从而提高程序的执行效率,特别是在处理 I/O 密集型任务时(如网络请求、文件读写)。


threading 的基本使用

threading 模块的核心是 Thread 类,我们将通过几个例子来学习如何使用它。

创建和启动线程(最简单的方式)

有两种主要的方式来创建线程:

传递一个函数给 Thread

import threading
import time
# 定义一个函数,这个函数将被线程执行
def print_numbers():
    for i in range(1, 6):
        print(f"线程 {threading.current_thread().name} 正在打印: {i}")
        time.sleep(1) # 模拟耗时操作
# 定义另一个函数
def print_letters():
    for letter in 'ABCDE':
        print(f"线程 {threading.current_thread().name} 正在打印: {letter}")
        time.sleep(1.5) # 模拟耗时操作
# --- 主程序 ---
# 创建线程对象
# target: 指定线程要执行的函数
# name: 给线程起个名字
thread1 = threading.Thread(target=print_numbers, name="数字线程")
thread2 = threading.Thread(target=print_letters, name="字母线程")
# 启动线程(这会让操作系统开始调度该线程)
# 注意:调用 start() 后,线程会立即开始执行,但不会马上执行完
thread1.start()
thread2.start()
# 等待线程执行完毕
# 如果没有 join(),主程序会继续往下执行,不会等待子线程
# 这会导致子线程还没跑完,主程序就退出了
thread1.join()
thread2.join()
print("所有线程执行完毕!")

输出分析: 你会发现数字和字母是交替打印的,而不是等一个完全打印完再打印另一个,这就是多线程并发执行的效果。

继承 threading.Thread

这种方式更面向对象,适合复杂的线程逻辑。

import threading
import time
class MyThread(threading.Thread):
    def __init__(self, name):
        # 必须调用父类的初始化方法
        super().__init__(name=name)
    # 必须重写 run() 方法,这是线程的入口点
    def run(self):
        for i in range(1, 4):
            print(f"自定义线程 {self.name} 正在运行: {i}")
            time.sleep(1)
# --- 主程序 ---
t = MyThread("自定义线程实例")
t.start() # 启动线程,它会自动调用 run() 方法
t.join() # 等待线程结束
print("自定义线程执行完毕!")

线程同步:避免“竞态条件”

当多个线程同时读写同一个共享资源(比如一个全局变量)时,就可能会发生“竞态条件”(Race Condition),导致数据不一致。

为了解决这个问题,我们需要使用“锁”(Lock)。

import threading
# 共享资源
shared_counter = 0
# 创建一个锁对象
lock = threading.Lock()
def increment_counter():
    global shared_counter
    for _ in range(100000):
        # 获取锁
        # 如果锁已经被其他线程获取,那么这个线程会在这里阻塞,直到锁被释放
        lock.acquire()
        try:
            # 在这 critical section (临界区) 内,代码是线程安全的
            shared_counter += 1
        finally:
            # 一定要释放锁!否则其他线程将永远等待
            # 使用 try...finally 可以确保锁一定会被释放,即使代码块内发生异常
            lock.release()
def decrement_counter():
    global shared_counter
    for _ in range(100000):
        with lock: # 'with' 语句是更推荐的方式,它会自动处理 acquire() 和 release()
            shared_counter -= 1
# --- 主程序 ---
t1 = threading.Thread(target=increment_counter)
t2 = threading.Thread(target=decrement_counter)
t1.start()
t2.start()
t1.join()
t2.join()
print(f"counter 的值是: {shared_counter}")
# 理想情况下,最终结果应该是 0,如果没有锁,结果可能是任何随机数。

with lock vs lock.acquire()/release():

  • with lock: 是 Python 的上下文管理器语法,更简洁、更安全,它会自动在代码块执行完毕后释放锁,即使代码块中抛出了异常。
  • lock.acquire()lock.release() 需要手动配对,容易忘记 release() 导致死锁。

线程间通信:使用 Queue

线程间直接共享变量和锁会比较复杂且容易出错,一个更安全、更高级的通信方式是使用 queue.Queue

Queue 是一个线程安全的队列,一个线程可以把数据“放入”队列,另一个线程可以从队列中“取出”数据。

import threading
import queue
import time
# 创建一个线程安全的队列
# maxsize=0 表示队列大小没有限制
q = queue.Queue()
def producer():
    """生产者线程:向队列中放入数据"""
    for i in range(1, 6):
        print(f"生产者: 准备放入数据 {i}")
        q.put(i) # put() 是阻塞的,如果队列满了,它会等待
        time.sleep(1)
def consumer():
    """消费者线程:从队列中取出数据"""
    while True:
        try:
            # get() 是阻塞的,如果队列是空的,它会等待
            # 可以设置超时时间,q.get(timeout=5)
            item = q.get()
            print(f"消费者: 取出数据 {item}")
            q.task_done() # 告诉队列这个任务已经处理完毕
            if item == 5: # 当取出特定数据后退出循环
                break
        except queue.Empty:
            print("队列已空,消费者退出。")
            break
# --- 主程序 ---
p = threading.Thread(target=producer, name="生产者")
c = threading.Thread(target=consumer, name="消费者")
p.start()
c.start()
# 等待生产者线程结束
p.join()
# 等待队列中所有任务都被处理完毕
# 如果消费者处理速度慢,主线程会在这里等待
q.join()
print("所有任务处理完毕!")

重要概念和注意事项

  1. GIL (Global Interpreter Lock - 全局解释器锁)

    • 这是 CPython (Python 的标准实现) 的一个特性。
    • 核心点:在任何时刻,只有一个线程在解释器中执行 Python 字节码
    • 对多线程的影响:这意味着 Python 的多线程并不能实现真正的“并行”(Parallelism)来利用多核 CPU 的优势,它更适合处理 I/O 密集型任务,因为当一个线程在等待 I/O(如网络请求)时,GIL 会被释放,其他线程就可以获得 GIL 并执行。
    • CPU 密集型任务:对于需要大量计算的任务(如数值计算),多线程由于 GIL 的存在,可能比单线程还慢,对于这类任务,应该使用 multiprocessing 模块,它会创建多个进程,每个进程有自己的 Python 解释器和内存空间,可以真正并行运行。
  2. 死锁

    • 当两个或多个线程互相等待对方释放锁时,就会发生死锁,导致所有线程都无法继续执行。
    • 避免方法
      • 尽量减少锁的持有时间。
      • 按照固定的顺序获取多个锁。
      • 使用 with 语句,确保锁一定会被释放。
  3. start() vs run()

    • thread.start()启动线程,让操作系统去调度执行,它会调用线程的 run() 方法。
    • thread.run()直接调用 run() 方法中的代码,这不会创建新的线程,而是在当前线程中同步执行代码。

功能 如何实现 何时使用
导入模块 import threading 永远是第一步
创建线程 threading.Thread(target=my_func)class MyThread(threading.Thread) 当需要并发执行任务时
启动线程 thread.start() 创建线程后,必须调用此方法
等待线程 thread.join() 当主线程需要等待子线程完成时
同步访问共享资源 with lock: 当多个线程读写同一个变量时,防止数据错乱
线程间安全通信 import queue
q = queue.Queue()
q.put() / q.get()
当一个线程需要向另一个线程传递数据时,这是推荐的方式
CPU 密集型任务 import multiprocessing 当任务计算量很大,需要利用多核 CPU 时

希望这份详细的指南能帮助你理解和使用 Python 的 threading 模块!

分享:
扫描分享到社交APP
上一篇
下一篇