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 有助于看懂底层原理和排查死锁。

相关推荐
郑州光合科技余经理1 小时前
国际版外卖系统:税率字段怎么和订单主流程解耦
android·java·开发语言·前端·后端·php·ai编程
软件课代表CiCi1 小时前
运行库是什么?电脑缺运行库怎么办?c++运行库缺失修复全攻略
开发语言·c++·windows·电脑·dll修复·dll丢失·运行库
Ivanqhz2 小时前
MLIR OpBuilder
开发语言·python·mlir
红宝村村长3 小时前
windows笔记本双系统ubuntu设置
开发语言
威联通安全存储3 小时前
TS-h2287XU-RP 在家电制造总装与质检数据场景的部署
python·制造
泡泡鱼(敲代码中)4 小时前
Python 字符串 str 完整学习笔记
python·学习
troy1284 小时前
Python 基础语法(八):Web 后端开发、数据分析与可视化、网络爬虫、人工智能 / 大模型应用
前端·python·数据分析
菜鸟~noob2334 小时前
【电子战】第15篇:混合定位——TDOA/FDOA/AOA 联合【含matlab代码】
开发语言·matlab
我不会起名字3225 小时前
一天一道算法题(34):回溯法的经典例题(子集)
java·数据结构·python·算法·golang·深度优先·力扣