Python多线程基础:从并发加速到线程安全与锁机制

白榆
白榆
发布时间:2026-10-12 | 4人浏览过
原创

在处理高延迟的 I/O 任务或需要并发执行的操作时,合理使用多线程能显著提升程序效率。本文通过实际代码演示如何从零构建多线程应用,逐步解决常见的资源竞争与状态同步问题,并提供基于 Lock 的安全并发方案。

利用多线程优化 I/O 密集型任务

当程序频繁依赖外部网络或磁盘读写时,CPU 会在等待响应时处于空闲状态。通过启动多个线程并行处理请求,可以有效掩盖等待时间,从而缩短整体运行周期。

第1步:编写单线程顺序执行对比基准

目的:观察单线程依次处理网络请求时的累计耗时。此时代码按顺序阻塞等待每个接口的返回值。

import time
import urllib.request

def get_responses():
    urls = [
        'http://www.baidu.com',
        'http://www.amazon.com',
        'http://www.ebay.com',
        'http://www.alibaba.com',
        'https://www.jb51.net'
    ]
    start = time.time()
    for url in urls:
        print(url)
        resp = urllib.request.urlopen(url)
        print(resp.getcode())
    print("Elapsed time: %s" % (time.time() - start))

get_responses()

执行后各链接依次返回状态码(如 200),总耗时为各次请求延迟的累加值。这说明在单线程模型下,CPU 无法在等待 A 接口返回时去请求 B 接口。

第2步:改造为多线程并发处理逻辑

目的:继承 Thread 类并重写 run 方法,使每个 URL 分配独立线程并行发起请求,大幅压缩总耗时。

import time
import urllib.request
from threading import Thread

class GetUrlThread(Thread):
    def __init__(self, url):
        self.url = url
        super(GetUrlThread, self).__init__()

    def run(self):
        resp = urllib.request.urlopen(self.url)
        print(self.url, resp.getcode())

def get_responses_thread():
    urls = [
        'http://www.baidu.com',
        'http://www.amazon.com',
        'http://www.ebay.com',
        'http://www.alibaba.com',
        'https://www.jb51.net'
    ]
    start = time.time()
    threads = []
    for url in urls:
        t = GetUrlThread(url)
        threads.append(t)
        t.start()  # 通知操作系统调度当前线程执行 run()
    
    for t in threads:
        t.join()   # 主线程等待所有子线程执行完毕后再继续
    print("Elapsed time: %s" % (time.time() - start))

get_responses_thread()

并发执行后,返回状态码的顺序不再固定,因为 CPU 切换调度的时机不可预测。但总耗时显著下降,证明多线程成功让 CPU 在等待网络响应时分心处理其他任务。注意 join() 的使用保证了计时器在所有请求完成后才触发。

排查共享变量的资源竞争问题

当多个线程共同访问并修改同一份数据时,极易产生并发修改冲突(Race Condition)。以下示例展示全局变量为何无法累积到预期值,并给出标准修复方案。

第3步:复现全局变量并发修改的竞争异常

from threading import Thread

some_var = 0

class IncrementThread(Thread):
    def run(self):
        global some_var
        read_value = some_var
        print("some_var in %s is %d" % (self.name, read_value))
        some_var = read_value + 1
        print("some_var in %s after increment is %d" % (self.name, some_var))

def use_increment_thread():
    threads = []
    for i in range(50):
        t = IncrementThread()
        threads.append(t)
        t.start()
    for t in threads:
        t.join()
    print("After 50 modifications, some_var should have become 50")
    print("After 50 modifications, some_var is %d" % some_var)

use_increment_thread()

多次运行会发现最终结果往往远小于 50。原因是在某个线程读取了旧值(如 15)后,CPU 可能立即切换至另一个线程,后者读到的仍是 15。两者分别加 1 后再写入,覆盖彼此的结果,导致数据丢失。这就是典型的线程不安全场景。

第4步:使用 Lock 锁定实现原子化操作

from threading import Lock, Thread

lock = Lock()
some_var = 0

class IncrementThread(Thread):
    def run(self):
        global some_var
        lock.acquire()          # 获取锁,独占临界区
        read_value = some_var
        print("some_var in %s is %d" % (self.name, read_value))
        some_var = read_value + 1
        print("some_var in %s after increment is %d" % (self.name, some_var))
        lock.release()          # 释放锁,允许其他线程进入

def use_increment_thread():
    threads = []
    for i in range(50):
        t = IncrementThread()
        threads.append(t)
        t.start()
    for t in threads:
        t.join()
    print("After 50 modifications, some_var should have become 50")
    print("After 50 modifications, some_var is %d" % some_var)

use_increment_thread()

加入 Lock 后,任意时刻仅有一个线程能执行读取、计算、写回的全流程。这确保了共享状态的逻辑原子性,多次运行均可稳定得到预期结果 50。在实际工程中,更推荐使用 with lock: 语法自动管理锁的释放。

验证线程隔离性与并发输出保护

Python 中每个线程拥有独立的调用栈,实例变量天然具备线程局部特性(Thread-local),不同线程不会互相篡改彼此的对象属性。但非局部的 I/O 输出仍可能被交织,需额外保护。

第5步:验证线程私有变量与打印同步机制

from threading import Thread, Lock
import time

lock = Lock()

class CreateListThread(Thread):
    def run(self):
        self.entries = []
        for i in range(10):
            time.sleep(1)       # 挂起当前线程,强制触发上下文切换
            self.entries.append(i)
        lock.acquire()          # 保护共享的输出设备(终端)
        print(self.entries)
        lock.release()

def use_create_list_thread():
    for i in range(3):
        t = CreateListThread()
        t.start()

use_create_list_thread()

若不加锁直接 print,由于三个线程交替执行,终端可能出现多段列表内容交叉拼凑的乱序现象。加上 Lock 后,打印操作成为串行化动作,确保每次输出的都是完整列表。该案例证明了线程间无法直接修改对方的局部变量,仅需关注对外部资源的同步访问。

总结

Python 多线程能够高效突破 I/O 瓶颈,但在共享内存交互时必须警惕竞态条件。掌握 threading.Thread 的生命周期控制、理解线程私有存储区的边界,并熟练使用 Lock 保护临界区,是编写稳定并发程序的基石。在实际业务中,若涉及大量 CPU 密集计算,还需结合 multiprocessing 模块绕过解释器的 GIL 限制。以上就是Python多线程从并发加速到线程安全的详细内容,更多关于Python基础开发技巧的资料请关注本站其它相关文章!

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表