Langchain系列文章目录
01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战
05-玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
06-从 0 到 1 掌握 LangChain Agents:自定义工具 + LLM 打造智能工作流!
python系列文章目录
01-Python 基础语法入门:从变量到输入输出,零基础也能学会!
02-Python 流程控制终极指南:if-else 和 for-while深度解析
03-Python 列表与元组全攻略:从新手到高手的必备指南
04-Python 字典与集合:从入门到精通的全面解析
05-Python函数入门指南:从定义到应用
06-Python 函数高级特性:从默认参数到闭包的全面解析
07-Python 模块与包:从零到自定义的全面指南
08-Python异常处理:从入门到精通的实用指南
09-Python 文件操作:从零基础到日志记录实战
10-Python面向对象编程入门:从类与对象到方法与属性
11-Python类的方法与属性:从入门到进阶的全面解析
12-Python继承与多态:提升代码复用与灵活性的关键技术
13-掌握Python魔法方法:如何用__add__和__len__自定义类的行为
14-python面向对象编程总结:从基础到进阶的 OOP 核心思想与设计技巧
15-掌握 Python 高级特性:深入理解迭代器与生成器
16-用 Python 装饰器提升效率:日志与权限验证案例
17-再也不怕资源泄漏!Python 上下文管理器,with语句全攻略
18-Python 标准库必备模块:math、random、os、json 全解析
19-Python 性能优化:从入门到精通的实用指南
20-Python内存管理与垃圾回收全解析
21-Python 代码调试与测试:从 pdb 到 TDD 的全面指南
22-Python 代码风格终极指南:从 PEP 8 到最佳实践全解析
23-Python实现网络通信:Socket模块与TCP/IP协议全解析
24-Python如何用requests库实现HTTP请求与响应?从零到实战全解析
25-并发编程基础:从线程到进程的Python实践
文章目录
前言
在现代编程中,并发编程是一项不可或缺的技能。无论是为了提升程序的运行效率,还是充分利用多核处理器的性能,掌握并发编程都能让你的代码更上一层楼。Python 提供了强大的工具,如 threading
和 multiprocessing
模块,让开发者能够轻松实现多线程和多进程编程。
本文将围绕并发编程基础展开,从线程与进程的概念入手,逐步深入到如何在 Python 中使用 threading
模块实现多线程,以及使用 multiprocessing
模块实现多进程。文章将通过通俗易懂的语言、清晰的代码示例和实际应用场景,帮助初学者快速入门,同时为进阶读者提供实用的技术参考。
一、并发编程概述
并发编程的核心在于如何高效地处理多个任务。本节将从基础概念出发,带你理解线程和进程的本质,以及它们在 Python 中的作用。
1.1 线程与进程的概念
线程和进程是并发编程的两个基本单位。理解它们的定义和区别,是掌握后续技术的基础。
1.1.1 什么是进程?
进程(Process)是操作系统分配资源的最小单位。每个进程都有独立的内存空间和系统资源。简单来说,你可以把进程想象成一个运行中的程序。比如,当你打开一个浏览器,它就是一个进程;再打开一个音乐播放器,又是一个新进程。
由于进程之间内存独立,数据交换需要通过管道或队列等机制,虽然安全性高,但通信成本也相对较大。
1.1.2 什么是线程?
线程(Thread)是进程内部的执行单元。一个进程可以包含多个线程,这些线程共享进程的内存和资源。打个比方,如果进程是一个工厂,那么线程就是工厂里的工人,多个工人共享工厂的设备,分工协作完成任务。
线程之间的通信更简单,但也容易因为共享资源而产生冲突,需要额外的同步机制来管理。
1.1.3 线程与进程的区别
以下是线程和进程的主要区别,方便大家快速记忆:
特性 | 进程 | 线程 |
---|---|---|
资源分配 | 独立内存空间 | 共享进程的内存空间 |
通信方式 | 需要特定机制(如队列) | 直接共享变量,通信简单 |
创建开销 | 较大,涉及资源分配 | 较小,基于现有进程 |
调度单位 | 操作系统调度 | CPU 调度 |
1.2 并发与并行的区别
在学习并发编程时,经常会听到“并发”和“并行”两个词,它们有何不同?
- 并发(Concurrency):指在同一时间段内,多个任务交替执行,看起来像是同时发生。比如,你一边听音乐一边写代码,CPU 在两个任务间快速切换。
- 并行(Parallelism):指在同一时刻,多个任务真正同时执行。这需要多核 CPU 或多台机器支持。
在 Python 中,threading
实现的是并发,而 multiprocessing
可以实现并行,因为它能利用多核 CPU。
二、使用 threading
模块实现多线程
Python 的 threading
模块是实现多线程的利器。它简单易用,适合处理 I/O 密集型任务。本节将从创建线程到线程同步,带你逐步掌握多线程编程。
2.1 创建线程
创建线程有两种常用方式,下面逐一介绍。
2.1.1 使用 Thread
类直接创建
你可以通过 threading.Thread
类创建一个线程,指定一个目标函数作为线程的任务。
import threading
def say_hello():
print(f"Hello from {threading.current_thread().name}")
# 创建线程,指定目标函数
thread = threading.Thread(target=say_hello, name="MyThread")
# 启动线程
thread.start()
# 等待线程结束
thread.join()
代码解析:
target=say_hello
:指定线程要执行的函数。name="MyThread"
:给线程取个名字,便于调试。start()
:启动线程,线程开始运行。join()
:主线程等待子线程结束。
运行结果可能是:
Hello from MyThread
2.1.2 通过继承 Thread
类创建
另一种方式是继承 threading.Thread
类,重写 run
方法。
import threading
class MyThread(threading.Thread):
def run(self):
print(f"Hello from {self.name}")
# 创建线程实例
thread = MyThread(name="CustomThread")
# 启动线程
thread.start()
# 等待线程结束
thread.join()
这种方式更灵活,适合需要封装线程逻辑的场景。
2.2 线程同步
多线程共享资源时,可能会出现“竞态条件”(多个线程同时修改同一变量,导致结果不可预测)。我们需要使用锁(Lock)来同步线程。
2.2.1 使用 Lock
避免竞态条件
下面是一个例子:两个线程同时对一个全局变量 counter
进行累加。
import threading
counter = 0
lock = threading.Lock()
def increment():
global counter
for _ in range(100000):
# 获取锁
lock.acquire()
counter += 1
# 释放锁
lock.release()
# 创建两个线程
t1 = threading.Thread(target=increment)
t2 = threading.Thread(target=increment)
# 启动线程
t1.start()
t2.start()
# 等待线程结束
t1.join()
t2.join()
print(f"Final counter: {counter}")
运行结果:
Final counter: 200000
代码解析:
lock.acquire()
:获取锁,确保同一时刻只有一个线程操作counter
。lock.release()
:释放锁,让其他线程继续执行。- 如果不加锁,
counter
的最终值可能会小于 200000,因为线程会“抢着”修改它。
2.2.2 使用 with
简化锁操作
为了避免忘记释放锁,Python 支持用 with
语句管理锁:
def increment():
global counter
for _ in range(100000):
with lock:
counter += 1
这种写法更简洁,自动处理锁的获取和释放。
2.2.3 常见问题:死锁
使用锁时要小心死锁(Deadlock),即多个线程互相等待对方释放锁,导致程序卡住。例如:
lock1 = threading.Lock()
lock2 = threading.Lock()
def thread1():
with lock1:
with lock2:
print("Thread 1")
def thread2():
with lock2:
with lock1:
print("Thread 2")
解决方法:
- 确保所有线程以相同的顺序获取锁。
- 使用超时机制,如
lock.acquire(timeout=1)
。
三、使用 multiprocessing
模块实现多进程
与线程不同,进程有独立的内存空间,适合 CPU 密集型任务。Python 的 multiprocessing
模块让多进程编程变得简单。
3.1 创建进程
创建进程的方式与线程类似,也有两种常用方法。
3.1.1 使用 Process
类直接创建
from multiprocessing import Process
def work():
print(f"Working in {Process().name}")
# 创建进程
p = Process(target=work)
# 启动进程
p.start()
# 等待进程结束
p.join()
运行结果:
Working in Process-1
3.1.2 通过继承 Process
类创建
from multiprocessing import Process
class MyProcess(Process):
def run(self):
print(f"Working in {self.name}")
# 创建进程实例
p = MyProcess()
# 启动进程
p.start()
# 等待进程结束
p.join()
3.2 进程间通信
由于进程间内存不共享,需要通过特定机制(如队列)传递数据。
3.2.1 使用 Queue
实现通信
下面是一个生产者-消费者模型的例子:
from multiprocessing import Process, Queue
def producer(queue):
for i in range(5):
queue.put(i)
print(f"Produced {i}")
def consumer(queue):
while True:
item = queue.get()
if item is None:
break
print(f"Consumed {item}")
# 创建队列
queue = Queue()
# 创建进程
p1 = Process(target=producer, args=(queue,))
p2 = Process(target=consumer, args=(queue,))
# 启动进程
p1.start()
p2.start()
# 等待生产者结束
p1.join()
# 发送结束信号
queue.put(None)
# 等待消费者结束
p2.join()
运行结果:
Produced 0
Produced 1
Produced 2
Produced 3
Produced 4
Consumed 0
Consumed 1
Consumed 2
Consumed 3
Consumed 4
代码解析:
queue.put(i)
:生产者将数据放入队列。queue.get()
:消费者从队列取出数据。queue.put(None)
:用 None 作为结束信号。
3.2.2 常见问题:队列阻塞
如果队列为空,queue.get()
会阻塞进程。解决方法是设置超时:
item = queue.get(timeout=1) # 等待1秒,超时抛出异常
四、总结
本文从并发编程的基础知识入手,详细讲解了线程和进程的概念,以及在 Python 中使用 threading
模块实现多线程和 multiprocessing
模块实现多进程的技术。通过丰富的代码示例和实际场景,你应该已经掌握了以下要点:
- 线程适合 I/O 密集型任务,多进程适合 CPU 密集型任务。
- 使用锁(Lock)解决线程间的竞态条件。
- 使用队列(Queue)实现进程间通信。
并发编程是 Python 开发者进阶的必备技能。希望本文能为你提供一个清晰的学习路径,鼓励你进一步探索异步编程等高级主题!