十六.线程池概念

1.什么是线程池

与进程池类似, 线程池是在系统启动时就先创建大量空闲的线程, 程序提交一个任务给线程池, 线程池便会调用一个线程来执行该任务, 当任务运行完毕后, 该线程并不会关闭, 而是返回到线程池中再次变为空闲状态等待下一个提交的任务,

2.为什么使用线程池

虽说线程的启动相比较于进程开销非常小, 但毕竟也是需要向操作系统发起调用, 我们使用线程在一些情况下能更好的提升性能, 尤其是程序中有大量生命期短暂的线程时, 使用线程池最为合适了

3.线程池的作用

使用线程池可以精确控制操作系统中并发线程的数量, 如果操作系统中有大量的并发线程, 并且没有限制数量, 那么就会导致操作系统的性能急剧下降, 甚至导致程序的崩溃, 而线程池可以通过控制最大线程数来解决该问题

十七.线程池的使用 (concurrent.futures 模块)

1.concurrent.futures 模块

concurrent.futures模块是Python标准库模块, 它提供了Executor类, 而 Executor又提供了两个子类,即ThreadPoolExecutorProcessPoolExecutor,其中ThreadPoolExecutor用于创建线程池,而ProcessPoolExecutor用于创建进程池, 也可以把这两个子类看作高度封装的异步调用接口

2.常用方法

方法

说明

submit(fn, *args, **kwargs)

将任务fn提交给进程池(异步提交), 后面的是参数

map(func, *iterables, timeout=None, chunksize=1)

类似于map函数, 升级版本开启多进程以异步的方式来对可迭代对象进行map处理,也类似于取代for循环submit的操作

shutdown(wait=True)

关闭线程池, (下面进行详解)

result(timeout=None)

获得该线程任务的结果, 如果任务未执行完会进行阻塞, 参数为超时时间

add_done_callback(fn)

为该线程设置回调, 当该线程完成任务时, 程序会自动触发fn函数

  • shutdown(wait=True) 详解:

相当于进程池的**pool.close( ) + pool.join( )**操作

wait=True, 等待池内所有任务执行完毕回收完资源后才继续

wait=False, 立即返回, 并不会等待池内的任务执行完毕

但不管wait参数为何值,整个程序都会等到所有任务执行完毕

submitmap必须在shutdown之前

注意 : 在用完一个线程池后,应该调用该线程池的**shutdown( )方法,该方法将启动线程池的关闭序列, 调用shutdown( )**方法后的线程池不再接收新任务, 但会将以前所有的已提交任务执行完成, 当线程池中的所有任务都执行完成后, 该线程池中的所有线程都会死亡

3.使用ThreadPoolExecutor线程池启动线程任务步骤

  • 使用ThreadPoolExecutor类创建一个线程池对象
  • 创建一个任务task (普通函数)
  • 调用线程池对象的**submit( )**方法来提交任务
  • 当没有任务的时候调用**shutdown( )**方法来关闭进程池

4.ThreadPoolExecutor使用示例

**ps 😗*ProcessPoolExecutor 与 ThreadPoolExecutor 的使用方法一模一样

from concurrent.futures import ThreadPoolExecutor
import os,time,random

def task(n):
    print(f"子线程:{os.getpid()}正在执行")
    time.sleep(random.randint(1,3))  # 模拟任务执行时间
    return n**2

if __name__ == '__main__':
    thread_pool = ThreadPoolExecutor(max_workers=4)  # 设置线程池大小
    futures = []
    for i in range(1,10):
        future = thread_pool.submit(task,i)  # 开启十个任务
        futures.append(future)
    thread_pool.shutdown(True)  # 关闭线程池,并等待任务结束

    for future in futures:
        print(future.result())  # 循环取出任务运行的结果(等到左右的任务执行完后才拿到)

'''输出
子线程:1360正在执行
子线程:1360正在执行
子线程:1360正在执行
子线程:1360正在执行
子线程:1360正在执行
子线程:1360正在执行
子线程:1360正在执行
子线程:1360正在执行
子线程:1360正在执行
1
4
9
16
25
36
49
64
81
'''

5.回调函数的使用 (add_done_callback( ))

  • submit( )提交任务future, 当future调用resule( )方法, 会阻塞当前主线程, 等到所有线程完成任务后, 该阻塞才会解除, 于是拿到的结果入上面的示例一样, 先运行完十个任务, 再拿到十个结果
  • 如果不想让resule( )方法将线程阻塞, 那么就可以使用futureadd_done_callback( )来添加回调函数, 当线程任务结束后, 程序会自动触发该回调函数, 并将future对象结果作为参数传给回调函数, 那我们可以在其内直接打印结果
from concurrent.futures import ThreadPoolExecutor
import os,time,random

def task(n):
    print(f"子线程:{os.getpid()}正在执行")
    time.sleep(random.randint(1,3))
    return n**2

def resule_back(res):
    print(res.result())  # 打印任务运行的结果(不需要等待其他线程任务完成)

if __name__ == '__main__':
    thread_pool = ThreadPoolExecutor(max_workers=4)
    for i in range(1,10):
        future = thread_pool.submit(task,i)
        future.add_done_callback(resule_back)  # 设置回调函数
    thread_pool.shutdown(True)  # 关闭线程池

'''输出
子线程:17164正在执行
子线程:17164正在执行
子线程:17164正在执行
子线程:17164正在执行
4
子线程:17164正在执行
1
子线程:17164正在执行
9
1625
子线程:17164正在执行
子线程:17164正在执行
36
子线程:17164正在执行
8164
49
'''

6.map( ) 方法的使用

  • map( ) 方法第一个参数是函数, 第二个是可迭代对象, 第三个是超时时间
  • 这种方法相当于是开启len(iterable)个线程, 等于是替代了 for + submit( ) 这两步
  • map( ) 方法得到的结果是一个生成器(generator)对象, 可以使用list( )函数造成列表
  • 优点 : 代码简单, 最后收集任务运行的结果, 仍然与传入参数的顺序一样
from concurrent.futures import ThreadPoolExecutor
import os,time,random

def task(n):
    print(f"子线程:{os.getpid()}正在执行")
    time.sleep(random.randint(1,3))
    return n**2

if __name__ == '__main__':
    thread_pool = ThreadPoolExecutor(max_workers=4)
    # 相当于替代了for+submit(),返回的是一个可list的对象
    future = thread_pool.map(task,range(1,10))  
    thread_pool.shutdown(True)  # 关闭线程池
    print(future)
    print(list(future))  # [1, 4, 9, 16, 25, 36, 49, 64, 81]

'''输出
子线程:1096正在执行
子线程:1096正在执行
子线程:1096正在执行
子线程:1096正在执行
子线程:1096正在执行
子线程:1096正在执行
子线程:1096正在执行
子线程:1096正在执行
子线程:1096正在执行
<generator object Executor.map.<locals>.result_iterator at 0x000001EDF4C72748>
[1, 4, 9, 16, 25, 36, 49, 64, 81]
'''

7.使用回调函数爬取各网站大小示例

from concurrent.futures import ThreadPoolExecutor
import requests,os

def get_htm(url):
    print(f"线程:{os.getpid()}正在获取网站:{url}源码")
    response = requests.get(url)
    if response.status_code == 200:
        return {"url":url,"text":response.text}
    else:
        return {"url":url,"text":""}

def parse_htm(back):
    res = back.result()
    print(f"线程:{os.getpid()}正在解析网站:{url}源码")
    with open("html_size.txt","a")as f:
        f.write(f"url:{res['url']},size:{len(res['text'])}\n")

if __name__ == '__main__':
    urls=[
        'https://zhuanlan.zhihu.com',
        '',
        'https://www.python.org',
        '',
        'http://www.china.com.cn',
    ]
    li = []
    thread_pool = ThreadPoolExecutor(3)
    for url in urls:
        future = thread_pool.submit(get_htm,url)
        future.add_done_callback(parse_htm)
        li.append(future)
    thread_pool.shutdown(True)
    
'''输出
线程:6392正在获取网站:https://zhuanlan.zhihu.com源码
线程:6392正在获取网站:https://www.python.org源码
线程:6392正在解析网站:http://www.china.com.cn源码
线程:6392正在解析网站:http://www.china.com.cn源码
线程:6392正在获取网站:http://www.china.com.cn源码
线程:6392正在解析网站:http://www.china.com.cn源码
线程:6392正在解析网站:http://www.china.com.cn源码
线程:6392正在解析网站:http://www.china.com.cn源码
'''
  • 查看下文件

python 多线程池 python多线程和线程池_concurrent模块