Python ThreadPoolExecutor 任务队列阻塞提交

分享一个多线程中阻塞提交任务队列的小例子,防止队列中任务过多导致内存占用过大,同时保证充分利用线程资源。

问题描述

  由于ThreadPoolExecutor默认采用的是无界队列,如果需要处理的任务量特别大,在生产速度大于消费速度时,可能会耗光系统资源,希望找到一种方式避免这种情况。

代码

  先不解释,直接上代码

# !/usr/bin/env python
# -*- coding: utf-8 -*-

from concurrent.futures import ThreadPoolExecutor, as_completed
import time
from random import randint


def test(num):
    for _ in range(2):
        time.sleep(randint(1, 5))   # 设置随机等待时间,
    return num


if __name__ == '__main__':
    max_pool = 5	# 线程池最大线程数
    executor = ThreadPoolExecutor(max_pool)
    all_task = []
    for i in range(50):		# 假设任务规模为50, 当然实际可能有上百上千万
        if len(all_task) < max_pool:
            all_task.append(executor.submit(test, i))
            print('创建:', i)
        else:
            for future in as_completed(all_task):
                all_task.remove(future)
                all_task.append(executor.submit(test, i))
                print('完成:', future.result(), '   创建:', i)
                break
    for future in as_completed(all_task):
        print('完成:', future.result())

解释

  使用 concurrent.futures.ThreadPoolExecutor 处理多线程任务的过程是首先将任务提交到一个任务队列中,若线程池中有线程执行完毕或者存在空闲线程时, 则从任务队列中拿取一个新的任务。若生产速度大于消费速度,会导致任务队列积压越来越多,占用过多内存。在消费速度无法提高时,可以通过降低生产速度即提交到任务队列的速度来解决该问题。因此本方法的核心就是监听线程池中有无任务执行完成,当线程池中有任务完成时再提交新的任务到队列中。

for future in as_completed(all_task):	# 返回已经执行结束的future实例
	all_task.remove(future)		# 从任务队列中移除
    all_task.append(executor.submit(test, i))	# 提交新的任务
    print('完成:', future.result(), '   创建:', i)
    break	# 这里只要监听到一个任务完成就跳出循环,否则上面提交的任务就重复了

  假设我们通过submit方法(返回一个Future实例)提交的任务都放在all_task列表中,使用as_completed来实现类似监听线程状态的功能。

concurrent.futures.as_completed(fs, timeout=None)
  Returns an iterator over the Future instances (possibly created by different Executor instances) given by fs that yields futures as they complete (finished or cancelled futures). Any futures given by fs that are duplicated will be returned once. Any futures that completed before as_completed() is called will be yielded first. The returned iterator raises a TimeoutError if __next__() is called and the result isn’t available after timeout seconds from the original call to as_completed(). timeout can be an int or float. If timeout is not specified or None, there is no limit to the wait time.

  简单来说,as_completed传入一个Futures类列表,会返回一个futures类的迭代器,通过迭代该迭代器可以获得已完成的futures。而且该方法一个比较好的特性是:as_completed会按线程完成的顺序返回,若没有线程完成,则会阻塞主进程,直到有线程结束执行。利用这个特性,我们就可以实现类似监听的功能,只要保证all_task中的线程和线程池中正在执行的线程保持同步就行,因此,当as_completed返回一个Future实例也就是线程执行结束时,我们从all_task中将该线程移除并提交一个新的线程,然后停止监听(break)进入下一个循环重新监听。
  通过这种方式,线程池中执行的线程始终保持在最大设定数,充分利用了线程,有剩余任务时不会有空闲线程存在,在任务队列等待的线程相当于没有,就防止了主线程一直往任务队列塞导致内存消耗过大。
  如果有问题恳请指正,有更好的实现方式欢迎评论讨论~

参考

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Python中的ThreadPoolExecutor线程池的一种实现方式,它提供了方便的接口来进行并发编程。在使用ThreadPoolExecutor时,通常会遇到异常捕获的问题。 当线程池中的线程执行任务时,如果任务发生异常,异常会被捕获,并通过Future对象返回给调用者。我们可以通过检查Future对象的状态来获取异常信息。Future对象是一个表示异步计算结果的对象,它可以用来检查任务是否完成、取消任务、获取任务的结果等。 在ThreadPoolExecutor中,可以通过submit方法来提交任务。这个方法返回一个Future对象,我们可以通过调用Future对象的result方法来等待任务完成并获取任务的结果。如果任务发生异常,result方法将会抛出异常,并将异常的类型和信息传递给调用者。 另外,我们还可以通过调用ThreadPoolExecutor的shutdown方法来关闭线程池。关闭线程池后,任何待处理的任务将会被取消,并且已提交但还未开始执行的任务将会被清除。我们可以通过调用Future对象的cancel方法来取消任务。 在代码中,我们可以使用try-except语句块来捕获线程任务中的异常。可以使用ThreadPoolExecutor的submit方法来提交任务,并通过返回的Future对象来获取任务的结果。在调用Future对象的result方法时,如果发生了异常,可以使用try-except语句块来捕获异常并处理异常。另外,在使用完线程池后,我们应该调用shutdown方法来关闭线程池,以释放资源。 总结起来,PythonThreadPoolExecutor提供了异常捕获机制,我们可以通过检查返回的Future对象来获取任务执行过程中的异常信息。在使用完线程池后,我们应该及时关闭线程池,以释放资源。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值