记忆碎片之python线程池、submit()、done()、result()、wait()、as_completed()、map()方法

多线程与线程池实战
本文深入探讨了多线程与线程池在处理不断增长任务时的应用,对比了threading模块与concurrent.futures模块的使用,详细解析了Future对象、线程状态查询、任务结果获取等关键概念,通过爬虫实例展示了如何有效管理和控制线程资源。

大量注释,小白一看就懂的多线程及参数使用

threadpool已经不再是主流,但是对于任务数量不断增加的程序,每有一个任务就生成一个线程,最终会导致线程数量的失控,例如,整站爬虫,假设初始只有一个链接a,那么,这个时候只启动一个线程,运行之后,得到这个链接对应页面上的b,c,d,,,等等新的链接,作为新任务,这个时候,就要为这些新的链接生成新的线程,线程数量暴涨。在之后的运行中,线程数量还会不停的增加,完全无法控制。所以,对于任务数量不端增加的程序,固定线程数量的线程池是必要的。
相比threading,该模块通过submit返回的是一个future对象,它是一个未来可期的对象
通过它可以获悉线程的状态,主线程或者主进程中可以获取某一个线程或进程执行的状态或者某一个任务执行的状态及返回值
主线程可以获取某一个线程或者任务的状态,以及返回值
当一个线程完成的时候,主线程能够立即知晓
让多线程和多进程的编码接口一致

from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import time


def spider(page):
    time.sleep(page)
    print(f"crawl task {page} finished")
    return page


# with ThreadPoolExecutor(max_workers=5) as t:  # 创建一个最大容纳量为5的线程池
#     task1 = t.submit(spider, 1)
#     task2 = t.submit(spider, 2)  # 通过submit提交执行的函数到线程池中
#     task3 = t.submit(spider, 3)
#
#     # 通过done来判断线程是否完成
#     print(f"task1: {task1.done()}")
#     print(f"task2: {task2.done()}")
#     print(f"task3: {task3.done()}")
#
#     time.sleep(2.5)
#
#     print(f"task1: {task1.done()}")
#     print(f"task2: {task2.done()}")
#     print(f"task3: {task3.done()}")
#     # 通过result()来获取返回值
#     print(task1.result())

# 使用with语句,通过ThreadPoolExecutor构造实例,
# 同时传入max_workers参数来设置线程池中最多能同时运行的线程数目

# 使用submit函数来提交线程需要执行的任务到线程池中,并返回该任务的句柄(类似文件、画图),
# 注意submit()不是阻塞的,而是立即返回的

# 通过使用done()方法判断该任务是否结束,提交任务后立即判断任务状态,显示都是未完成,在显示2.5秒后
# task1和task2执行完毕,task3仍然在执行中

# 使用result()方法可以获取任务的返回值

# 方法和参数
# wait(fs, timeout=None, return_when=All_COMPLETED)
# fs 表示需要执行的序列
# timeout 等待的最大时间,如果超过这个时间
# return_when 表示wait返回结果的结果,默认为ALLP_COMPLETED全部执行完成后再返回结果

from concurrent.futures import ThreadPoolExecutor, wait, FIRST_COMPLETED, ALL_COMPLETED


def spider2(page):
    time.sleep(page)
    print(f"crawl task {page} finished")
    return page


# with ThreadPoolExecutor(max_workers=5) as t:
#     all_task = [t.submit(spider2, page) for page in range(1, 5)]
#     # 返回条件 FIRST_COMPLETED 当第一个任务完成的时候就停止等待,继续主线程任务,所以紧接着打印了“结束”
#     wait(all_task, return_when=FIRST_COMPLETED)
#     print("结束")
#     # 设置延时(等待)时间2.5秒
#     print(wait(all_task, timeout=2.5))
#     # 所以最后只有task4还在运行

# as_completed
# 虽然使用return_when=FIRST_COMPLETED判断任务是否结束,但是不能在主线程中一直判断
# 最好的办法是当某个任务结束来,就给主线程返回结果,而不是一直判断每个任务是否结束
# as_completed就是当子线程中的任务执行完成后,直接用result()获取返回结果

from concurrent.futures import as_completed


def spider3(page):
    time.sleep(page)
    print(f"crawl task {page} finished")
    return page


def main():
    with ThreadPoolExecutor(max_workers=5) as t:
        obj_list = []
        for page in range(1, 5):
            obj = t.submit(spider3, page)
            obj_list.append(obj)
        for future in as_completed(obj_list):
            data = future.result()
            print(f"main:{data}")


# main()    使用过后发现,这个多线程比上面的测试耗时都多一些
# as_completed()方法是一个生成器,在没有任务完成的时候就会一直阻塞,除非设置了timeout
# 当某个任务完成的时候,会yield这个任务,就能执行for循环下面的语句,然后继续阻塞程序,直到所有任务结束
# 同时,先完成的任务会先返回给主线程

# map
# map(fn, *iterables, timeout=None)
# fn 需要线程执行的函数
# iterables 接收一个可迭代对象
# 和wait()的timeout一样,用于延时,但是map是返回线程执行的结果,如果timeout小于线程执行时间会抛出TimeoutError

def spider4(page):
    time.sleep(page)
    print(f"crawl task {page} finished")
    return page


def main2():
    executor = ThreadPoolExecutor(max_workers=4)
    i = 1
    # 列表中的每一个元素都执行来spider4()函数,并分配各线程池   task1:2
    for result in executor.map(spider4, [2, 3, 1, 4]):
        print(f"task{i}:{result}")
        i += 1


main2()
# 使用map方法,无需提前使用submit方法,与Python高阶函数map含义相同,都是将序列中的每个元素都执行同一个行数
# 与as_completed()方法的结果不同,输出顺序和列表的顺序相同,
# 就算1秒的任务执行完成,也会先打印前面提交的任务返回的结果

# map可以保证输出的顺序, submit输出的顺序是乱的
# 如果你要提交的任务的函数是一样的,就可以简化成map。但是假如提交的任务函数是不一样的,
# 或者执行的过程之可能出现异常(使用map执行过程中发现问题会直接抛出错误)就要用到submit()
# submit和map的参数是不同的,submit每次都需要提交一个目标函数和对应的参数,
# map只需要提交一次目标函数,目标函数的参数放在一个迭代器(列表,字典)里就可以。

### `concurrent.futures.wait` 的使用方法 `concurrent.futures.wait(fs, timeout=None, return_when=ALL_COMPLETED)` 用于等待 `Future` 实例完成,这些实例可能由多个不同的执行器实例创建,通过 `fs` 指定这些 `Future` 实例。它会返回一个具名元组,该元组有两个元素,每个元素都是一个集合。第一个元素名叫 `done`,该集合包括已完成的 `futures`;第二个元素名叫 `not_done`,该集合包括未完成的 `futures`。 `timeout` 用来控制返回之前等待的最大秒数,可以是整数或者浮点数。如果不指定或为 `None`,则不限制等待时间。`return_when` 指明函数何时应该返回,它必须是下列常量之一: - `FIRST_COMPLETED`:函数在任意一个 `future` 完成或者被取消时返回。 - `FIRST_EXCEPTION`:函数在任意一个 `future` 因为异常而结束时返回。如果没有 `future` 抛出异常,它等价于 `ALL_COMPLETED`。 - `ALL_COMPLETED`:当所有 `future` 完成或者被取消时函数才会返回 [^2]。 以下是一个简单的示例代码: ```python import concurrent.futures import time def task(n): time.sleep(n) return n with concurrent.futures.ThreadPoolExecutor() as executor: futures = [executor.submit(task, i) for i in range(5)] done, not_done = concurrent.futures.wait(futures, timeout=2, return_when=concurrent.futures.FIRST_COMPLETED) print(f"已完成的任务数量: {len(done)}") print(f"未完成的任务数量: {len(not_done)}") ``` ### `concurrent.futures.as_completed` 的使用方法 要把 `Executor.submit` 方法和 `futures.as_completed` 函数结合起来使用。这个组合比 `executor.map` 更灵活,因为 `submit` 方法能处理不同的可调用对象和参数,而 `executor.map` 只能处理参数不同的同一个可调用对象。此外,传给 `futures.as_completed` 函数的 `Future` 集合可以来自多个 `Executor` 实例,例如一些由 `ThreadPoolExecutor` 实例创建,另一些由 `ProcessPoolExecutor` 实例创建 [^3]。 以下是一个简单的示例代码: ```python import concurrent.futures import time def task(n): time.sleep(n) return n with concurrent.futures.ThreadPoolExecutor() as executor: futures = [executor.submit(task, i) for i in range(5)] for future in concurrent.futures.as_completed(futures): result = future.result() print(f"任务结果: {result}") ``` ### 两者的区别 - **返回值不同**:`wait` 函数返回一个具名元组,包含已完成和未完成的 `Future` 集合;而 `as_completed` 函数返回一个迭代器,按 `Future` 完成的顺序逐个返回 `Future` 对象。 - **使用场景不同**:`wait` 更适合需要根据任务完成状态进行批量处理的场景,例如等待部分或全部任务完成后再进行下一步操作;`as_completed` 更适合需要实时处理完成任务结果的场景,一旦有任务完成就可以立即获取结果进行处理。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值