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

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

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只需要提交一次目标函数,目标函数的参数放在一个迭代器(列表,字典)里就可以。

发布了46 篇原创文章 · 获赞 7 · 访问量 4621

猜你喜欢

转载自blog.csdn.net/Python_DJ/article/details/105193300