从初级到高级的介绍一下 python 的threading用法

threading 是 Python 用来处理多线程的工具,简单说就是让程序同时干多件事儿,比如一边下载文件一边播放音乐。

初级:啥是线程?怎么跑起来?

1. 理解线程:多干点活儿

想象你在饭店点餐:普通程序(单线程)就像服务员只盯着你,等你吃完再去招呼别人;多线程呢,就像饭店有好几个服务员,同时招呼好几桌客人。threading 就是帮你“雇”几个服务员,让程序并行干活。

2. 最简单的起步:跑个线程

Python 自带 threading 模块,不用装啥,直接开干。咱先写个最简单的多线程代码:

import threading
import time

def say_hello():
    print("Hello")
    time.sleep(1)  # 假装干活1秒
    print("World")

# 创建线程
t = threading.Thread(target=say_hello)
# 启动线程
t.start()
# 主线程继续干活
print("主线程不等你,我先跑了")

跑一下,输出可能是:

Hello
主线程不等你,我先跑了
(等1秒)
World

threading.Thread(target=函数):创建一个线程,告诉它要跑哪个函数。

start():启动线程,让它开始干活。

主线程(程序默认的线程)和新线程一起跑,谁先谁后看运气。

3. 等线程干完:join

有时候你想让主线程等等其他线程干完再走,用 join():

import threading
import time

def task():
    print("任务开始")
    time.sleep(2)
    print("任务结束")

t = threading.Thread(target=task)
t.start()
t.join()  # 主线程在这等着
print("线程干完了,我再跑")

输出:

任务开始
(等2秒)
任务结束
线程干完了,我再跑

join():让主线程等着,直到这个线程干完。

4. 多线程一起跑

再加几个线程试试:

import threading
import time

def worker(name):
    print(f"工人 {name} 开始干活")
    time.sleep(1)
    print(f"工人 {name} 干完了")

threads = []
for i in range(3):
    t = threading.Thread(target=worker, args=(i,))
    threads.append(t)
    t.start()

for t in threads:
    t.join()  # 等所有线程干完

print("所有工人下班了")

输出可能是:

工人 0 开始干活
工人 1 开始干活
工人 2 开始干活
(等1秒)
工人 0 干完了
工人 1 干完了
工人 2 干完了
所有工人下班了

args=(i,):给线程函数传参数。

多个线程几乎同时启动,总耗时是1秒,而不是3秒(1+1+1)。

中级:整点实用的活儿

1. 线程安全的共享数据:锁

多线程一起跑有个坑:如果大家同时改同一块数据,可能乱套。比如计数器:

import threading

counter = 0

def add():
    global counter
    for _ in range(100000):
        counter += 1

t1 = threading.Thread(target=add)
t2 = threading.Thread(target=add)
t1.start()
t2.start()
t1.join()
t2.join()
print("计数器结果:", counter)

你可能期待结果是 200000(两个线程各加 100000),但实际可能是 180000 之类的。为什么?因为线程抢着改 counter,会“撞车”。

  • 解决办法:加锁*
import threading

counter = 0
lock = threading.Lock()  # 创建锁

def add():
    global counter
    for _ in range(100000):
        with lock:  # 上锁
            counter += 1

t1 = threading.Thread(target=add)
t2 = threading.Thread(target=add)
t1.start()
t2.start()
t1.join()
t2.join()
print("计数器结果:", counter)

输出:

计数器结果:200000

threading.Lock():创建一个锁。 with lock

threading 的 Event 的用法

啥是 Event?

想象你在组织一个饭局,大家都到了才能开吃。Event 就像饭店老板娘,等所有人到齐,她喊一声“开饭啦”,大家才动筷子。在线程里,Event 是一个信号工具:

默认是“红灯”(未设置状态,False),线程看到红灯就等着。 有人把信号设成“绿灯”(True),等着的人就知道可以干活了。

基础用法:红灯绿灯 1. 创建和简单使用

import threading
import time

# 创建一个 Event 对象
event = threading.Event()

def waiter():
    print("小弟等着老板喊开工")
    event.wait()  # 等绿灯
    print("老板喊了,小弟开干!")

def boss():
    print("老板先忙别的")
    time.sleep(2)  # 假装忙2秒
    print("老板喊:开工啦!")
    event.set()  # 红灯变绿灯

# 创建线程
t1 = threading.Thread(target=waiter)
t2 = threading.Thread(target=boss)
t1.start()
t2.start()
t1.join()
t2.join()

输出:

小弟等着老板喊开工
老板先忙别的
(等2秒)
老板喊:开工啦!
小弟开干!

threading.Event():创建一个信号对象,默认是红灯(False)。

event.wait():线程在这等着,直到信号变绿(True)。

event.set():把信号设成绿灯,通知所有等着的人。

2. 检查信号状态

可以用 is_set() 看看当前是红灯还是绿灯:

import threading

event = threading.Event()
print("信号状态:", event.is_set())  # False,红灯
event.set()
print("信号状态:", event.is_set())  # True,绿灯

输出:

信号状态:False
信号状态:True

3. 关掉绿灯:clear

信号变绿后,可以用 clear() 把它变回红灯:

import threading
import time

event = threading.Event()

def worker():
    print("工人等着信号")
    event.wait()
    print("信号绿了,干活啦")
    time.sleep(1)
    print("活干完了")

t = threading.Thread(target=worker)
t.start()
time.sleep(1)
print("老板点绿灯")
event.set()  # 绿灯
time.sleep(2)
print("老板关绿灯")
event.clear()  # 变回红灯
print("信号状态:", event.is_set())
t.join()

输出:

工人等着信号
(等1秒)
老板点绿灯
信号绿了,干活啦
(等1秒)
活干完了
老板关绿灯
信号状态:False

clear():把绿灯变回红灯,适合重复使用的场景。

实用场景:协调多线程

1. 等所有人准备好再开工

比如三个工人得等老板一声令下一起干活:

import threading
import time

event = threading.Event()

def worker(name):
    print(f"工人 {name} 准备好了")
    event.wait()  # 等信号
    print(f"工人 {name} 开始干活")

# 创建3个工人线程
threads = []
for i in range(3):
    t = threading.Thread(target=worker, args=(i,))
    threads.append(t)
    t.start()

time.sleep(1)  # 假装老板磨蹭一下
print("老板喊:全体开工!")
event.set()  # 绿灯,所有人一起干

for t in threads:
    t.join()

输出:

工人 0 准备好了
工人 1 准备好了
工人 2 准备好了
(等1秒)
老板喊:全体开工!
工人 0 开始干活
工人 1 开始干活
工人 2 开始干活

一个 Event 可以控制多个线程,信号一变,所有等着的人都动。

2. 加个超时:别傻等

有时候不想无限等,可以给 wait() 加个超时:

import threading
import time

event = threading.Event()

def lazy_worker():
    print("懒工人等着信号")
    if event.wait(timeout=2):  # 最多等2秒
        print("信号来了,干活")
    else:
        print("等烦了,不干了")

t = threading.Thread(target=lazy_worker)
t.start()
time.sleep(3)  # 老板故意拖延
print("老板姗姗来迟")
event.set()
t.join()

输出:

懒工人等着信号
(等2秒)
等烦了,不干了
(再等1秒)
老板姗姗来迟

wait(timeout=秒数):等够时间没绿灯就返回 False,继续往下走。

高级用法:生产者-消费者模式

Event 在生产者和消费者场景里很常见,比如一个线程生产数据,另一个消费数据:

import threading
import time
import random

event = threading.Event()
data = None

def producer():
    global data
    for i in range(3):
        time.sleep(1)
        data = f"货物 {i}"
        print(f"生产者生产了 {data}")
        event.set()  # 通知消费者
        event.wait()  # 等消费者拿走
        event.clear()  # 重置信号

def consumer():
    for _ in range(3):
        event.wait()  # 等生产者信号
        print(f"消费者拿到了 {data}")
        time.sleep(random.uniform(0.5, 1))  # 模拟消费时间
        event.set()  # 通知生产者我拿完了
        event.clear()  # 重置信号

t1 = threading.Thread(target=producer)
t2 = threading.Thread(target=consumer)
t1.start()
t2.start()
t1.join()
t2.join()

输出(时间随机):

生产者生产了 货物 0
消费者拿到了 货物 0
生产者生产了 货物 1
消费者拿到了 货物 1
生产者生产了 货物 2
消费者拿到了 货物 2

生产者用 set() 通知有货,消费者用 set() 通知拿完了。 用 clear() 重置信号,确保每次交易干净利落。

文档信息

版权声明:可自由转载(请注明转载出处)-非商用-非衍生

发表时间:2025年7月1日 10:23