python3 多线程 多进程 多协程
1. 多线程、多进程、多协程的对比
1.1 多进程 Process(multiprocessing)
- 优点:可以利用多核CPU并行运算
- 缺点:占用资源最多、可以启动的数目比线程少
- 适用于:CPU密集型计算
1.2 多线程 Thread(threading)
一个进程包括多个线程
- 优点:相比进程,更轻量级、占用资源少
- 缺点:
- 相比进程:多线程只能并发执行,只能运用单核CPU,不能利用多核CPU
- 相比协程:启动数目有限制、占用内存资源、有线程切换开销
- 适用于:IO密集型计算、同时运行的任务数目要求不多
1.3 多协程 Coroutine(asyncio)
一个线程包含多个协程
- 优点:内存开销最少、启动协程数目最多
- 缺点:支持的库有限制、代码实现复杂
- 适用于:IO密集型计算、需要超多运行、有现成库支持的场景
2. 根据任务选择对应技术
3. 实现代码
3.1 多进程
引入包
import multiprocessing
3.1.1 创建一个进程
# 创建进程
multi = multiprocessing.Process(
name="进程1", # 指定进程名称
target=is_prime, # 指定进程运行的方法
args=(i,) # 运行方法所需要的参数
)
# 运行进程
multi.start()
# 等待进程运行完毕
multi.join()
3.1.2 进程锁
import multiprocessing, time
class Account:
def __init__(self, balance):
self.balance = balance
# 生成锁
lock = multiprocessing.Lock()
def draw_money(account, money):
# 使用锁
with lock:
if money <= account.balance:
time.sleep(0.1)
print("取钱成功", multiprocessing.current_process().name)
account.balance -= money
print("取钱成功,余额:", account.balance)
else:
print("取钱失败,余额:", account.balance)
def draw_money01(account, money):
# 落锁
lock.acquire()
if money <= account.balance:
time.sleep(0.1)
print("取钱成功", multiprocessing.current_process().name)
account.balance -= money
print("取钱成功,余额:", account.balance)
else:
print("取钱失败,余额:", account.balance)
# 开锁
lock.release()
account = Account(1000)
a1 = multiprocessing.Process(
target=draw_money01,
args=(account, 800,)
)
a2 = multiprocessing.Process(
target=draw_money01,
args=(account, 800,)
)
a1.start()
a2.start()
3.1.3 进程池
# 引入包
from concurrent.futures import ProcessPoolExecutor
# 参数集合
PRIMES = [112239349340123] * 100
# 进程要执行的方法
def is_prime(n):
if n < 2:
return False
if n == 2:
return True
if n % 2 == 0:
return False
sqrt_n = int(math.floor(math.sqrt(n)))
for i in range(3, sqrt_n + 1, 2):
if n % i == 0:
return False
return True
# 使用进程池 map方法
# 使用with 结构 进程执行完会自动回收
with ProcessPoolExecutor() as pool:
# result 是进程池中所有进程执行完成后的结果集合
result = pool.map(
is_prime, # 进程执行的方法
PRIMES # list集合 这是一个参数集合,有多少个参数 就代表会创建多少个进程
)
for res in result:
print(res)
# 使用进程池 submit方法
with ProcessPoolExecutor() as pool:
# 进程列表
futures = {}
# 循环参数集合
for i in PRIMES:
# submit 函数第一个参数是要执行的方法,第二个为参数,submit方法是一个一个执行的
# future 返回的不是进程执行的结果 是一个进程执行的状态 只有进程执行完才会返回进程执行的结果
future = pool.submit(is_prime,i)
futures[future] = i
# as_completed 表示进程池中执行完一个就输出一个的值 不按照进程池进入的顺序输出结果
for future in concurrent.futures.as_completed(futures):
i = futures[future]
print(i,future.result())
3.1.4 在Web服务中使用进程池
import json, math, flask
from concurrent.futures import ProcessPoolExecutor
# 创建一个flask对象
app = flask.Flask(__name__)
# 函数
def is_prime(n):
if n < 2:
return False
if n == 2:
return True
if n % 2 == 0:
return False
sqrt_n = int(math.floor(math.sqrt(n)))
for i in range(3, sqrt_n + 1, 2):
if n % i == 0:
return False
return True
@app.route("/is_prime/<numbers>")
def index(numbers):
number_list = [int(x) for x in numbers.split(",")]
# 使用map函数向线程池提交多个方法并传递参数
result = process_pool.map(is_prime, number_list)
# 返回结果
return json.dumps(dict(zip(number_list, result)))
if __name__ == '__main__':
# 创建一个进程池 进程池必须再__main__中
process_pool = ProcessPoolExecutor()
app.run()
3.2 多线程
引入包:
import threading
3.2.1 创建一个线程
thread = threading.Thread(
name = "线程1",
target=爬虫.Spider01, # 线程执行的方法
args=(url,) # 执行方法需要带的参数
)
# 运行线程
thread.start()
# 等待线程运行完毕
thread.join()
3.2.2 线程锁
import threading, time
class Account:
def __init__(self, balance):
self.balance = balance
# 生成锁
lock = threading.Lock()
def draw_money(account, money):
# 使用锁
with lock:
if money <= account.balance:
time.sleep(0.1)
print("取钱成功", threading.current_thread().name)
account.balance -= money
print("取钱成功,余额:", account.balance)
else:
print("取钱失败,余额:", account.balance)
def draw_money01(account, money):
# 落锁
lock.acquire()
if money <= account.balance:
time.sleep(0.1)
print("取钱成功", threading.current_thread().name)
account.balance -= money
print("取钱成功,余额:", account.balance)
else:
print("取钱失败,余额:", account.balance)
# 开锁
lock.release()
account = Account(1000)
a1 = threading.Thread(
target=draw_money01,
args=(account, 800,)
)
a2 = threading.Thread(
target=draw_money01,
args=(account, 800,)
)
a1.start()
a2.start()
3.2.3 线程池
# 引用包
from concurrent.futures import ThreadPoolExecutor
# 线程池需要的方法
urls = [
f"https://www.cnblogs.com/#{page}"
for page in range(1,20+1)
]
# 线程要执行的方法
def Spider(url):
req = requests.get(url)
return req.text
# 使用线程池 map方法
with ThreadPoolExecutor() as pool:
# map函数第一个参数为要执行的方法,第二个参数为参数列表,参数列表的数量决定方法执行的次数
htmls = pool.map(Spider,urls)
# zip方法是将第一个list集合的每个值与集合中的每个值拼接成元组(两个集合数量要一致)
htmls = list(zip(urls,htmls))
for url,html in htmls:
print(url,len(html))
# 使用线程池 submit方法
with ThreadPoolExecutor() as pool:
futures = {}
for url,html in htmls:
# submit 函数第一个参数是要执行的方法,第二个为参数,submit方法是一个一个执行的
future = pool.submit(html_parse,html)
futures[future] = url
# 这种获取返回值的方法是等待线程执行完再输出返回值
# for future in futures:
# url = futures[future]
# print(url,future.result())
# as_completed 方法是线程池中执行完一个方法输出一个返回值,可能会造成顺序不一致
for future in concurrent.futures.as_completed(futures):
url = futures[future]
print(url,future.result())
3.2.4 在Web服务中使用线程池
import json,time,flask
from concurrent.futures import ThreadPoolExecutor
# 创建一个flask
app = flask.Flask(__name__)
# 创建一个线程池
pool = ThreadPoolExecutor()
# 延时方法
def api_def():
time.sleep(0.1)
return "api_def"
# 延时方法
def file_def():
time.sleep(0.2)
return "file_def"
# 延时方法
def db_def():
time.sleep(0.3)
return "db_def"
# 首页
@app.route("/")
def index():
# 往线程池添加一个执行的函数
file_result = pool.submit(file_def)
# 往线程池添加一个执行的函数
api_result = pool.submit(api_def)
# 往线程池添加一个执行的函数
db_result = pool.submit(db_def)
# 获取执行的结果
return json.dumps({
"file_result": file_result.result(),
"api_result": api_result.result(),
"db_result": db_result.result()
})
if __name__ == '__main__':
app.run()
3.3 多协程
引入包:
import asyncio
3.3.1 使用多协程
# 创建协程方法
async def main():
print("这是")
await asyncio.sleep(1)
print("多协程")
# 运行
asyncio.run(main())
3.3.2 使用协程池爬取网页
需要用到包:
pip install aiohttp
代码:
import asyncio, aiohttp, time
# 创建一个协程函数
async def async_craw(url):
# 创建客户端对象
async with aiohttp.ClientSession() as session:
# 根据url获取对象
async with session.get(url) as resp:
# 获取结果
result = await resp.text()
# 返回结果
print(f"url:{url},text:{len(result)}")
# 获取协程池
pool = asyncio.get_event_loop()
# url列表
urls = [
f"https://www.cnblogs.com/#{page}"
for page in range(1,20+1)
]
# 创建任务列表
task = [
pool.create_task(async_craw(url)) for url in urls
]
start = time.time()
# 等待任务列表全部执行完成
pool.run_until_complete(asyncio.wait(task))
end = time.time()
print("time:", end - start)
3.3.3 控制协程池数量
import asyncio, aiohttp, time, 爬虫
# 设置并发数量
semaphore = asyncio.Semaphore(5)
async def async_craw(url):
# 使用并发数量
async with semaphore:
# 创建客户端对象
async with aiohttp.ClientSession() as session:
# 根据url获取服务器返回对象
async with session.get(url) as resp:
# 获取服务器返回内容
result = await resp.text()
print(f"url:{url},text:{len(result)}")
# 协程池
pool = asyncio.get_event_loop()
# 创建任务集合
task = [
pool.create_task(async_craw(url)) for url in 爬虫.urls
]
start = time.time()
# 执行任务池 并等待任务池执行完成
pool.run_until_complete(asyncio.wait(task))
end = time.time()
print("time:", end - start)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· Qt个人项目总结 —— MySQL数据库查询与断言