一、为什么需要多线程?
在日常编程中,单线程程序执行任务时常常会因等待某些操作(比如文件读取、网络请求、数据库等)而"卡住"。多线程可以让你的程序一边处理A业务,一边等待/执行B业务,提升效率、改善用户体验。
常见使用场景:
- 网络爬虫高并发抓取
- 显示进度条同时下载/计算
- 聊天、应用服务端、并发IO操作等
二、多线程的状态以及分类
线程创建之后,并不是始终保持一个状态的,其状态大概如下:
- New 创建
- Runnable 就绪。等待调度
- Running 运行
- Blocked 阻塞。阻塞可能在 Wait Locked Sleeping
- Dead 消亡
线程的类型大致分为下面几种:
- 主线程
- 子线程
- 守护线程(后台线程)
- 前台线程
三、Python如何实现多线程?
Python的多线程主要依靠threading
标准库模块。
来几个简单例子快速入门:
1. 最简单的多线程
python
import threading
def worker():
print("线程正在运行", threading.current_thread().name)
t = threading.Thread(target=worker, name='MyThread')
t.start() # 启动线程
t.join() # 等待线程结束
解释:
Thread
创建一个线程。start()
启动线程,join()
主线程等待子线程结束。
2. 多线程并发
python
import threading
import time
def task(name):
print(f"{name} 开始工作")
time.sleep(2)
print(f"{name} 完成工作")
threads = []
for i in range(3):
t = threading.Thread(target=task, args=(f'线程{i+1}',))
t.start()
threads.append(t)
for t in threads:
t.join()
print("所有线程结束")
多个线程可同时执行,主线程等待所有线程完结。
四、认识GIL:为什么Python多线程不能提速CPU密集型任务?
GIL(全局解释器锁)是Python CPython解释器的"互斥锁",同一时刻只有一个线程能执行Python字节码。
所以io密集型(爬虫、文件、网络)多线程极其有效,但CPU密集型任务常无效(建议多进程)。
CPU密集型程序演示
python
import threading
import time
def cpu_task():
total = 0
for _ in range(10**7):
total += 1
threads = [threading.Thread(target=cpu_task) for _ in range(4)]
start = time.time()
for t in threads:
t.start()
for t in threads:
t.join()
print("用时:", time.time() - start)
可以试对比用单线程与多线程时间,几乎没有明显提升。
五、共享资源与锁机制
多线程易出现竞态条件问题:多个线程同时操作同一变量时,可能产生不可控的错误。
举例:线程安全导致的数据异常
python
import threading
count = 0
def add():
global count
for _ in range(100000):
count += 1
threads = [threading.Thread(target=add) for _ in range(2)]
for t in threads:
t.start()
for t in threads:
t.join()
print(count) # 有很大概率小于200000
解决:加锁(Lock)
python
lock = threading.Lock()
count = 0
def safe_add():
global count
for _ in range(100000):
with lock:
count += 1
threads = [threading.Thread(target=safe_add) for _ in range(2)]
for t in threads:
t.start()
for t in threads:
t.join()
print(count) # 必定是200000
with lock:
能保证同一时刻只有一个线程进入代码块,杜绝数据错乱。
六、更高级:守护线程与条件变量
1. 守护线程(Daemon Thread)
主线程退出时,守护线程自动结束。
python
import threading
import time
def worker():
while True:
print('工作中...')
time.sleep(1)
t = threading.Thread(target=worker)
t.daemon = True # 设置为守护线程,主线程结束时子线程也会退出
t.start()
time.sleep(3)
print("主线程结束") # 子线程会跟随退出
2. Thread子类实现(面向对象)
python
class MyThread(threading.Thread):
def run(self):
print("自定义线程运行")
t = MyThread()
t.start()
t.join()
3. 条件变量/信号量(Condition/Semaphore)
用于更复杂的线程同步和通信,可以实现"生产者-消费者"模式、任务队列处理等。
python
import threading
import time
condition = threading.Condition()
data = []
def producer():
for i in range(3):
with condition:
data.append(i)
print("生产:", i)
condition.notify()
time.sleep(1)
def consumer():
for _ in range(3):
with condition:
while not data:
condition.wait()
print("消费:", data.pop(0))
t1 = threading.Thread(target=producer)
t2 = threading.Thread(target=consumer)
t1.start()
t2.start()
t1.join()
t2.join()
七、典型应用场景案例
1. 多线程爬虫IO密集案例
python
import threading
import requests
def fetch(url):
resp = requests.get(url)
print(f"{url} 响应长度: {len(resp.text)}")
urls = [
'https://www.baidu.com',
'https://www.python.org',
'https://www.github.com',
]
threads = [threading.Thread(target=fetch, args=(u,)) for u in urls]
for t in threads:
t.start()
for t in threads:
t.join()
print("所有网页抓取完成")
大大加快多个网页的抓取速度。
2. 线程池简化并发任务
官方库concurrent.futures.ThreadPoolExecutor推荐用法:
python
from concurrent.futures import ThreadPoolExecutor
def square(n):
return n*n
with ThreadPoolExecutor(max_workers=5) as executor:
results = list(executor.map(square, range(10)))
print(results)
更方便地发起大量并发任务和收集异步结果。
点个赞,关注我获取更多实用 Python 技术干货!如果觉得有用,记得收藏本文!