Python使用ThreadPoolExecutor线程池和Queue消息队列
1 介绍
(1)线程池
concurrent.futures模块是从 Python3.2 后引入的异步执行模块,主要用于在多核CPU和网络I/O中进行高效的并发编程。这个模块提供了ThreadPoolExecutor(线程池)和ProcessPoolExecutor(进程池)两个类。
可使用concurrent.futures.ThreadPoolExecutor直接创建线程池,需要设置最大的线程数,用submit()执行线程。
(2)消息队列
Python中的Queue是线程安全的消息队列,可使用queue.Queue完成消息队列。
put()和get()配合使用,可使用put()创建消息,使用get()消费消息,使用qsize()获取消息长度。
join()和task_done()配合使用,join()用于阻塞调用线程,直到队列中的所有任务被处理掉;task_done()用于通知队列任务已完成,每一个由put()调用入队的任务都有一个对应的task_done()调用。一般在使用join()阻塞线程时才会使用task_done()。
(3)进程和线程的区别
文件追加时线程和进程是不安全的。
进程的最大特点:独立性。 每个进程都拥有自己独立的资源。适合CPU 密集、高可靠性、隔离需求。
线程的最大特点:共享性。 同一进程下的多个线程共享绝大部分资源,能高效协同工作。适合I/O 密集、低延迟、资源共享。
核心原因:
“追加到文件末尾”这个操作不是原子的,它包含几个步骤:
- 定位末尾 - 找到文件结尾位置
- 写入数据 - 在找到的位置写入内容
- 更新信息 - 更新文件大小等元数据
问题发生过程:
- 线程A定位到末尾(比如位置100)
- 线程B也定位到末尾(同样是位置100)
- 线程A写入数据,文件变长
- 线程B还在原来的位置100写入,覆盖了线程A的数据
2 源代码
代码说明:
线程thread_task.py和进程process_task.py的写法很明显。线程可以共享资源,所以可以定义全局变量,在多个线程中使用。进程独享资源,定义全局变量无法在多个进程中共享使用,所以,只能通过参数传递把数据传递到各个进程中。
2.1 线程池thread_pool_task.py
import random
from concurrent.futures import ThreadPoolExecutor
from queue import Queue
from threading import Lock
from time import sleep
# 创建线程池
# max_workers:最大线程池的数量
max_workers = 3
executor = ThreadPoolExecutor(max_workers=max_workers)
# 添加锁
lock = Lock()
# 构建消息队列,Queue是线程安全的
queue_task = Queue()
# 数据库(假设)
db_record = list()
def run_task(msg):
# 添加消息
queue_task.put(msg)
if queue_task.qsize() > max_workers:
return
# 执行线程池
executor.submit(doing_task)
def doing_task():
# 消费消息队列中的任务
while queue_task.qsize() > 0:
# 获取消息
msg = queue_task.get()
print("start-" + str(msg))
# 跟新数据库(假设)
# 添加锁
lock.acquire()
# 更新
db_record.append(msg)
# 释放锁
lock.release()
# 随机睡眠1-3秒
sleep(random.randint(1, 3))
print("end-" + str(msg))
if __name__ == '__main__':
run_task(1)
run_task(2)
run_task(3)
run_task(4)
run_task(5)
run_task(6)
2.2 线程thread_task.py
import random
from concurrent.futures import ThreadPoolExecutor
from queue import Queue
from threading import Lock
from time import sleep
# 创建线程池
# max_workers:最大线程池的数量
max_workers = 3
executor = ThreadPoolExecutor(max_workers=max_workers)
# 添加锁
lock = Lock()
# 构建消息队列,Queue是线程安全的
queue_task = Queue()
def save_one_json(json_str: str):
# 追加1条数据
with open("./data_thread.json", "a", encoding="utf-8", errors="ignore") as json_file:
json_file.write(json_str)
json_file.write("\n")
def doing_task():
# 消费消息队列中的任务
while queue_task.qsize() > 0:
# 获取消息
msg = queue_task.get()
print("start-" + str(msg))
# 跟新数据库(假设)
# 添加锁
lock.acquire()
# 更新
save_one_json(msg)
# 释放锁
lock.release()
# 随机睡眠1-3秒
sleep(random.randint(1, 3))
print("end-" + str(msg))
def add_msg():
# 添加消息
queue_task.put(str({"key": "111111111111,河南大学创立于1912年,始名河南留学欧美预备学校。后历经中州大学、国立开封中山大学、省立河南大学等阶段,1942年升格为国立河南大学。1952年院系调整 ,校本部更名为河南师范学院。后经开封师范学院、河南师范大学等阶段,1984年恢复河南大学校名 。2000年6月,原河南大学、开封医学高等专科学校、开封师范高等专科学校合并组建新的河南大学。2012年,河南大学入选第一批卓越医生教育培养计划项目试点高校;入选国家级卓越法律人才教育培养基地;入选第一批国家卓越医生教育培养计划项目试点高校。"}))
queue_task.put(str({"key": "222222222222,河南大学创立于1912年,始名河南留学欧美预备学校。后历经中州大学、国立开封中山大学、省立河南大学等阶段,1942年升格为国立河南大学。1952年院系调整 ,校本部更名为河南师范学院。后经开封师范学院、河南师范大学等阶段,1984年恢复河南大学校名 。2000年6月,原河南大学、开封医学高等专科学校、开封师范高等专科学校合并组建新的河南大学。2012年,河南大学入选第一批卓越医生教育培养计划项目试点高校;入选国家级卓越法律人才教育培养基地;入选第一批国家卓越医生教育培养计划项目试点高校。"}))
queue_task.put(str({"key": "333333333333,河南大学创立于1912年,始名河南留学欧美预备学校。后历经中州大学、国立开封中山大学、省立河南大学等阶段,1942年升格为国立河南大学。1952年院系调整 ,校本部更名为河南师范学院。后经开封师范学院、河南师范大学等阶段,1984年恢复河南大学校名 。2000年6月,原河南大学、开封医学高等专科学校、开封师范高等专科学校合并组建新的河南大学。2012年,河南大学入选第一批卓越医生教育培养计划项目试点高校;入选国家级卓越法律人才教育培养基地;入选第一批国家卓越医生教育培养计划项目试点高校。"}))
queue_task.put(str({"key": "444444444444,河南大学创立于1912年,始名河南留学欧美预备学校。后历经中州大学、国立开封中山大学、省立河南大学等阶段,1942年升格为国立河南大学。1952年院系调整 ,校本部更名为河南师范学院。后经开封师范学院、河南师范大学等阶段,1984年恢复河南大学校名 。2000年6月,原河南大学、开封医学高等专科学校、开封师范高等专科学校合并组建新的河南大学。2012年,河南大学入选第一批卓越医生教育培养计划项目试点高校;入选国家级卓越法律人才教育培养基地;入选第一批国家卓越医生教育培养计划项目试点高校。"}))
queue_task.put(str({"key": "555555555555,河南大学创立于1912年,始名河南留学欧美预备学校。后历经中州大学、国立开封中山大学、省立河南大学等阶段,1942年升格为国立河南大学。1952年院系调整 ,校本部更名为河南师范学院。后经开封师范学院、河南师范大学等阶段,1984年恢复河南大学校名 。2000年6月,原河南大学、开封医学高等专科学校、开封师范高等专科学校合并组建新的河南大学。2012年,河南大学入选第一批卓越医生教育培养计划项目试点高校;入选国家级卓越法律人才教育培养基地;入选第一批国家卓越医生教育培养计划项目试点高校。"}))
queue_task.put(str({"key": "666666666666,河南大学创立于1912年,始名河南留学欧美预备学校。后历经中州大学、国立开封中山大学、省立河南大学等阶段,1942年升格为国立河南大学。1952年院系调整 ,校本部更名为河南师范学院。后经开封师范学院、河南师范大学等阶段,1984年恢复河南大学校名 。2000年6月,原河南大学、开封医学高等专科学校、开封师范高等专科学校合并组建新的河南大学。2012年,河南大学入选第一批卓越医生教育培养计划项目试点高校;入选国家级卓越法律人才教育培养基地;入选第一批国家卓越医生教育培养计划项目试点高校。"}))
def run_task():
for i in range(max_workers):
print("thread =", str(i+1))
# 执行线程池
executor.submit(doing_task)
if __name__ == '__main__':
# 添加消息
add_msg()
# 执行任务
run_task()
2.3 进程process_task.py
from multiprocessing import Process, Lock, Queue
import random
from time import sleep
def save_one_json(json_str: str):
# 追加1条数据
with open("./data_process.json", "a", encoding="utf-8", errors="ignore") as json_file:
json_file.write(json_str)
json_file.write("\n")
def doing_task(lock, queue_task):
print(queue_task.qsize())
# 消费消息队列中的任务
while queue_task.qsize() > 0:
# 获取消息
msg = queue_task.get()
print("start-" + str(msg))
# 跟新数据库(假设)
# 添加锁
lock.acquire()
# 更新
save_one_json(msg)
# 释放锁
lock.release()
# 随机睡眠1-3秒
sleep(random.randint(1, 2))
print("end-" + str(msg))
def add_msg():
# 构建消息队列,Queue是线程安全的
queue_task = Queue()
# 添加消息
queue_task.put(str({"key": "111111111111,河南大学创立于1912年,始名河南留学欧美预备学校。后历经中州大学、国立开封中山大学、省立河南大学等阶段,1942年升格为国立河南大学。1952年院系调整 ,校本部更名为河南师范学院。后经开封师范学院、河南师范大学等阶段,1984年恢复河南大学校名 。2000年6月,原河南大学、开封医学高等专科学校、开封师范高等专科学校合并组建新的河南大学。2012年,河南大学入选第一批卓越医生教育培养计划项目试点高校;入选国家级卓越法律人才教育培养基地;入选第一批国家卓越医生教育培养计划项目试点高校。"}))
queue_task.put(str({"key": "222222222222,河南大学创立于1912年,始名河南留学欧美预备学校。后历经中州大学、国立开封中山大学、省立河南大学等阶段,1942年升格为国立河南大学。1952年院系调整 ,校本部更名为河南师范学院。后经开封师范学院、河南师范大学等阶段,1984年恢复河南大学校名 。2000年6月,原河南大学、开封医学高等专科学校、开封师范高等专科学校合并组建新的河南大学。2012年,河南大学入选第一批卓越医生教育培养计划项目试点高校;入选国家级卓越法律人才教育培养基地;入选第一批国家卓越医生教育培养计划项目试点高校。"}))
queue_task.put(str({"key": "333333333333,河南大学创立于1912年,始名河南留学欧美预备学校。后历经中州大学、国立开封中山大学、省立河南大学等阶段,1942年升格为国立河南大学。1952年院系调整 ,校本部更名为河南师范学院。后经开封师范学院、河南师范大学等阶段,1984年恢复河南大学校名 。2000年6月,原河南大学、开封医学高等专科学校、开封师范高等专科学校合并组建新的河南大学。2012年,河南大学入选第一批卓越医生教育培养计划项目试点高校;入选国家级卓越法律人才教育培养基地;入选第一批国家卓越医生教育培养计划项目试点高校。"}))
queue_task.put(str({"key": "444444444444,河南大学创立于1912年,始名河南留学欧美预备学校。后历经中州大学、国立开封中山大学、省立河南大学等阶段,1942年升格为国立河南大学。1952年院系调整 ,校本部更名为河南师范学院。后经开封师范学院、河南师范大学等阶段,1984年恢复河南大学校名 。2000年6月,原河南大学、开封医学高等专科学校、开封师范高等专科学校合并组建新的河南大学。2012年,河南大学入选第一批卓越医生教育培养计划项目试点高校;入选国家级卓越法律人才教育培养基地;入选第一批国家卓越医生教育培养计划项目试点高校。"}))
queue_task.put(str({"key": "555555555555,河南大学创立于1912年,始名河南留学欧美预备学校。后历经中州大学、国立开封中山大学、省立河南大学等阶段,1942年升格为国立河南大学。1952年院系调整 ,校本部更名为河南师范学院。后经开封师范学院、河南师范大学等阶段,1984年恢复河南大学校名 。2000年6月,原河南大学、开封医学高等专科学校、开封师范高等专科学校合并组建新的河南大学。2012年,河南大学入选第一批卓越医生教育培养计划项目试点高校;入选国家级卓越法律人才教育培养基地;入选第一批国家卓越医生教育培养计划项目试点高校。"}))
queue_task.put(str({"key": "666666666666,河南大学创立于1912年,始名河南留学欧美预备学校。后历经中州大学、国立开封中山大学、省立河南大学等阶段,1942年升格为国立河南大学。1952年院系调整 ,校本部更名为河南师范学院。后经开封师范学院、河南师范大学等阶段,1984年恢复河南大学校名 。2000年6月,原河南大学、开封医学高等专科学校、开封师范高等专科学校合并组建新的河南大学。2012年,河南大学入选第一批卓越医生教育培养计划项目试点高校;入选国家级卓越法律人才教育培养基地;入选第一批国家卓越医生教育培养计划项目试点高校。"}))
return queue_task
def run_task(queue_task: Queue):
# 创建进程池
# max_workers:最大线程池的数量
"""
注意:
本案例使用ProcessPoolExecutor时必须序列化参数,否则无法运行。
使用进程池可以传递基础数据类型(普通字符串、list、元组和map等),其他类型需要序列化
executor = ProcessPoolExecutor(max_workers=max_workers)
"""
max_workers = 3
# 添加锁
lock = Lock()
for i in range(max_workers):
print("process =", str(i + 1))
# 执行线程池
Process(target=doing_task, args=(lock, queue_task)).start()
print("test")
if __name__ == '__main__':
# 添加消息
queue_task_tmp = add_msg()
# 执行任务
run_task(queue_task_tmp)
3449

被折叠的 条评论
为什么被折叠?



