Python使用ThreadPoolExecutor线程池和Queue消息队列

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 密集、低延迟、资源共享。

核心原因:

“追加到文件末尾”这个操作不是原子的,它包含几个步骤:

  1. 定位末尾 - 找到文件结尾位置
  2. 写入数据 - 在找到的位置写入内容
  3. 更新信息 - 更新文件大小等元数据

问题发生过程:

  1. 线程A定位到末尾(比如位置100)
  2. 线程B也定位到末尾(同样是位置100)
  3. 线程A写入数据,文件变长
  4. 线程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)

Python的`concurrent.futures`模块是Python标准库中的一个异步执行工具,它提供了两个高级API:`ThreadPoolExecutor``ProcessPoolExecutor`,分别用于实现线程池进程池。线程池可以用来管理一组工作线程,可以提交任务给这些工作线程执行,并且可以控制这些工作线程的并发数量。队列则是用来在工作线程之间传递数据的。 当你想要组合使用线程池队列时,可以利用`ThreadPoolExecutor`中的`submit`方法提交任务到线程池,而任务函数中则可以使用队列来获取或发送数据。这种方式在多线程环境下处理数据时非常有用,因为它可以安全地在多个线程之间共享数据,而不需要担心线程安全问题。 下面是一个简单的示例: ```python from concurrent.futures import ThreadPoolExecutor, as_completed from queue import Queue # 定义一个任务函数,它从队列中取出任务并处理 def worker(task_queue): while not task_queue.empty(): # 从队列中获取任务 task = task_queue.get() # 进行处理 print(f"Processing {task}") # 告知队列任务处理完成 task_queue.task_done() # 创建一个任务队列 task_queue = Queue() # 向队列中添加一些任务 for i in range(5): task_queue.put(f"Task {i}") # 创建线程池 with ThreadPoolExecutor(max_workers=3) as executor: # 将任务分配给线程池处理 for _ in range(task_queue.qsize()): executor.submit(worker, task_queue) # 等待所有任务完成 task_queue.join() print("所有任务处理完毕") ``` 在这个示例中,我们首先创建了一个任务队列,并向队列中添加了一些任务。然后创建了一个线程池,并将工作线程提交给线程池来执行`worker`函数,该函数会从队列中取出任务并进行处理。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值