Python 并发 线程

线程/threading

流程

导入threading模块去→创建线程对象→启动线程。

基础

多线程执行顺序不确定导致结果错误,需要加锁。

由于 Python 有 GIL(全局解释器锁),单个字节码操作是原子的,但像 ticket -= 1 这种"读取→计算→写回"多步操作仍会被打断,所以仍需自己加锁。

实例

基础

python 复制代码
import threading
import time

# 定义线程要执行的函数
def worker(name, delay):
    for i in range(3):
        print(f"线程 {name} 正在运行: {i}")
        time.sleep(delay)
    print(f"线程 {name} 结束")

# 1. 创建线程对象
t1 = threading.Thread(target=worker, args=("A", 1))
t2 = threading.Thread(target=worker, args=("B", 0.5))

# 2. 启动线程
t1.start()
t2.start()

# 主线程等待子线程结束(可选)
t1.join()
t2.join()

print("所有线程执行完毕")
复制代码
线程 A 正在运行: 0
线程 B 正在运行: 0
线程 B 正在运行: 1
线程 A 正在运行: 1
线程 B 正在运行: 2
线程 A 正在运行: 2
线程 B 结束
线程 A 结束
所有线程执行完毕

资源共享

资源共享,为保证运行正常需要加锁。

不加锁版
python 复制代码
import threading
import time

ticket = 10  # 共享资源:10 张票

def sell(name):
    global ticket
    while ticket > 0:
        time.sleep(0.01)          # 模拟售票耗时
        ticket -= 1
        print(f"{name} 卖出一张,剩余: {ticket}")

t1 = threading.Thread(target=sell, args=("窗口A",))
t2 = threading.Thread(target=sell, args=("窗口B",))

t1.start()
t2.start()
t1.join()
t2.join()

print("最终剩余票数:", ticket)
复制代码
窗口A 卖出一张,剩余: 9
窗口B 卖出一张,剩余: 8
窗口A 卖出一张,剩余: 7
窗口B 卖出一张,剩余: 6
窗口A 卖出一张,剩余: 5
窗口B 卖出一张,剩余: 4
窗口A 卖出一张,剩余: 3
窗口B 卖出一张,剩余: 2
窗口A 卖出一张,剩余: 1
窗口B 卖出一张,剩余: 0
窗口A 卖出一张,剩余: -1   ← 出现了负数!
最终剩余票数: -1
加锁版

lock.acquire(timeout=2) 可设置锁的超时时间,以秒为单位。

python 复制代码
import threading
import time

ticket = 10
lock = threading.Lock()   # 创建锁

def sell(name):
    global ticket
    while True:
        with lock:                 # 加锁,保证同一时刻只有一个线程操作
            if ticket <= 0:
                break
            time.sleep(0.01)
            ticket -= 1
            print(f"{name} 卖出一张,剩余: {ticket}")

t1 = threading.Thread(target=sell, args=("窗口A",))
t2 = threading.Thread(target=sell, args=("窗口B",))

t1.start()
t2.start()
t1.join()
t2.join()

print("最终剩余票数:", ticket)
复制代码
窗口A 卖出一张,剩余: 9
窗口B 卖出一张,剩余: 8
窗口B 卖出一张,剩余: 7
窗口A 卖出一张,剩余: 6
窗口A 卖出一张,剩余: 5
窗口B 卖出一张,剩余: 4
窗口B 卖出一张,剩余: 3
窗口A 卖出一张,剩余: 2
窗口A 卖出一张,剩余: 1
窗口B 卖出一张,剩余: 0
最终剩余票数: 0
使用try...finally
python 复制代码
import threading
import time

ticket = 10
lock = threading.Lock()

def sell(name):
    global ticket
    while True:
        lock.acquire()          # 手动加锁
        try:
            if ticket <= 0:
                break
            time.sleep(0.01)
            ticket -= 1
            print(f"{name} 卖出一张,剩余: {ticket}")
        finally:
            lock.release()      # 无论是否异常,都释放锁

t1 = threading.Thread(target=sell, args=("窗口A",))
t2 = threading.Thread(target=sell, args=("窗口B",))

t1.start()
t2.start()
t1.join()
t2.join()

print("最终剩余票数:", ticket)
复制代码
窗口A 卖出一张,剩余: 9
窗口B 卖出一张,剩余: 8
窗口B 卖出一张,剩余: 7
窗口A 卖出一张,剩余: 6
窗口A 卖出一张,剩余: 5
窗口B 卖出一张,剩余: 4
窗口B 卖出一张,剩余: 3
窗口A 卖出一张,剩余: 2
窗口A 卖出一张,剩余: 1
窗口B 卖出一张,剩余: 0
最终剩余票数: 0

总结

减少锁的范围,只给"修改共享资源的代码块"上锁,不要给整个函数上锁,会影响效率。

避免多把锁,尽量用一把锁解决问题,多把锁会导致死锁。

写法 优点 缺点
with lock: 简洁、自动释放、异常安全 不够直观看到加解锁
try...finally 显式控制,逻辑清晰 代码稍长,容易忘记写 finally

with lock: 本质上就是 try...finally 的语法糖,日常推荐用 with;但理解 try...finally 有助于看懂底层原理和排查死锁。

相关推荐
IT方大同4 分钟前
java输入输出+方法
java·开发语言·python
滕州市燕猫虎计算机科技工作室个体工商户6 分钟前
《Rust程序设计》学习笔记一
开发语言·rust·学习笔记·rust程序设计
Wang's Blog30 分钟前
Java框架 SpringCloud 快速入门: 服务拆分之服务远程调用
java·开发语言·spring cloud
努力努力再努力wz31 分钟前
【CUDA入门系列】CUDA编程实战:设备选择、显存管理、Kernel、Stream 与 Event 全流程梳理
开发语言·c++
杨丰玮41842 分钟前
C语言核心语法
java·c语言·开发语言·学习方法
ttwuai44 分钟前
Go开源后台管理系统推荐:README、源码和License应该怎么验
开发语言·golang·开源
Java后端的Ai之路1 小时前
Python 进阶探索30 - Python中的装饰器
开发语言·人工智能·python·文件处理·装饰器模式
沫璃染墨1 小时前
《从零入门Linux系统篇(五十八):线程篇·十一——线程安全与死锁详解:从可重入到多锁管理》
linux·服务器·开发语言·c++·驱动开发·安全·架构
黑妹天下第一乖1 小时前
第 05 讲:阿加犀 AidCV 图像处理加速与 OpenCV 一致开发实战
开发语言·图像处理·人工智能·嵌入式硬件·数码相机·opencv·计算机视觉
言乐61 小时前
Python基于关键词分拣快递(适用于电商与货运代理等)
开发语言·python·django·virtualenv·pygame