Python线程池与进程池

前言

  前面我们已经将线程并发编程与进程并行编程全部摸了个透,其实我第一次学习他们的时候感觉非常困难甚至是吃力。因为概念实在是太多了,各种锁,数据共享同步,各种方法等等让人十分头痛。所以这边要告诉你一个好消息,前面的所有学习的知识点其实都是为本章知识点做铺垫,在学习了本章节的内容后关于如何使用多线程并发与多进程并行就采取本章节中介绍的方式即可。

  这里要介绍一点与之前内容不同的地方,即如果使用队列进行由进程池创建的进程之间数据共享的话不管是multiprocessing模块下的Queue还是queue模块下的Queue都不能为进程池中所创建的进程进行数据共享,我们需要用到另一个队列即multiprocessing.Manager()中的Queue当然这个我也会在下面介绍到。那么开始学习吧!

   官方文档

执行器

  最早期的Python2中是没有线程池这一概念的,只有进程池。直到Python3的出现才引入了线程池,其实关于他们的使用都是非常简单,而且接口也是高度统一甚至说一模一样的。而线程池与进程池的作用即是为了让我们能够更加便捷的管理线程或进程。

我们先说一下,如果需要使用线程池或进程池,需要导入模块concurrent.futures

  from concurrent.futures import ThreadPoolExecutor # 线程池执行器

  from concurrent.futures import ProcessPoolExecutor # 进程池执行器

  这里介绍一下,关于线程池或者进程池创建出的线程与进程与我们使用multiprocessing模块或者threading模块中创建的线程或进程有什么区别。我们以多线程为例:

import threading

def task():
ident = threading.get_ident()
print(ident)
# 销毁当前执行任务的线程 if __name__ == '__main__': for i in range(10):
t1 = threading.Thread(target=task,) # 领任务
t1.start() # 等待CPU调度,而不是立即执行 # 执行 # ==== 执行结果 ==== Ps:可以看到每个线程的id号都不一样,这也印证了图上说的。 """
10392
12068
5708
13864
2604
7196
7324
9728
9664
472
"""
import threading
from concurrent.futures import ThreadPoolExecutor # 线程池执行器 def task():
ident = threading.get_ident()
print(ident)
# 结束任务,不销毁当前执行任务的线程,直到所有任务都执行完毕。 if __name__ == '__main__':
pool = ThreadPoolExecutor(max_workers=2) # 这里代表有2个线程可以领取任务
for i in range(10):
pool.submit(task) # 执行器启动任务,将这些任务给2个人分配,也就是说task这个任务会被这2个线程不断的执行,直到执行完毕后这2个线程才会死亡 # ==== 执行结果 ==== Ps:可以看到这里都让这2个线程把任务接了,内存开销相比于上面的要小。 """
7272
7272
7272
7272
11596
7272
11596
11596
11596
11596
"""

方法大全


执行器方法大全 
submit(fn, *args, **kwargs) 调度可调用对象 fn,以 fn(*args **kwargs) 方式执行并返回 Future 对像代表可调用对象的执行。(异步提交!极为牛逼!)
map(func, *iterables, timeout=None, chunksize=1) 类似于 map(func, *iterables)
shutdown(wait=True) 等待,类似join()方法,并且在所有的任务完成后关闭执行器。wait=True为关闭,为False则是不关闭执行器的意思。
Ps:其实对于线程池或进程池来说,他们的池都有一个官方的名称叫做执行器,接口都是一样的。那么接下来我就将线程池进程池这样的名字换做执行器了,也是方便理解。 

基本使用


  其实关于执行器的使用,我们有两种方式,一种是依赖于with语句,一种是不依赖于with语句,那么我在这里推荐使用依赖于wait语句的执行器。

  不依赖于with语句的执行器使用:

import threading
from concurrent.futures import ThreadPoolExecutor # 线程池执行器 def task():
print("执行了") if __name__ == '__main__':
pool = ThreadPoolExecutor(max_workers=2) # 这里代表有2个线程可以领取任务 , 对于线程池来讲它是默认值是CPU核心数+4,对于进程池来讲最大开启的进程数是CPU核心数。
for i in range(10):
pool.submit(task) # 执行器启动任务,将这些任务给2个人分配,也就是说task这个任务会被这2个线程不断的执行,直到执行完毕后这2个线程才会死亡 # ==== 执行结果 ==== Ps:可以看到这里都让这2个线程把任务接了,内存开销相比于上面的要小。 """
执行了
执行了
执行了
执行了
执行了
执行了
执行了
执行了
执行了
执行了
"""

  依赖于with语句的执行器使用:

import threading
from concurrent.futures import ThreadPoolExecutor # 线程池执行器 def task():
print("执行了")
# 销毁 if __name__ == '__main__':
with ThreadPoolExecutor(max_workers=2) as pool: # 这里代表有2个线程可以领取任务 , 对于线程池来讲它是默认值是CPU核心数+4,对于进程池来讲最大开启的进程数是CPU核心数。
for i in range(10):
pool.submit(task) # 执行器启动任务,将这些任务给2个人分配,也就是说task这个任务会被这2个线程不断的执行,直到执行完毕后这2个线程才会死亡 # ==== 执行结果 ==== Ps:可以看到这里都让这2个线程把任务接了,内存开销相比于上面的要小。 """
执行了
执行了
执行了
执行了
执行了
执行了
执行了
执行了
执行了
执行了
"""

期程对象

方法大全


期程对象(由执行器执行的任务的返回结果)方法大全 
方法/属性名称 功能描述
cancel() 尝试取消调用。 如果调用正在执行或已结束运行不能被取消则该方法将返回 False,否则调用会被取消并且该方法将返回 True
cancelled() 如果调用成功取消返回 True
running() 如果调用正在执行而且不能被取消那么返回 True
done() 如果调用已被取消或正常结束那么返回 True
result(timeout=None) 即获取任务的返回结果,最大等待timeout秒,如不设置则死等,超时触发CancelledError异常。
add_done_callback(fn) 增加回调函数fn,这个fn应该至少有一个形参来接收当前期程对象。
exception(timeout=None) 返回由调用引发的异常。如果调用还没完成那么这个方法将等待 timeout 秒。如果在 timeout 秒内没有执行完成,concurrent.futures.TimeoutError将会被触发。timeout 可以是整数或浮点数。如果 timeout 没有指定或为 None,那么等待时间就没有限制。
Ps:还有一些期程对象的方法没有举例出来。详情参见文档 

期程对象的作用


  我们可以看到,我们上面的函数并没有返回值,如果有返回值的话怎么办呢?

import threading
from concurrent.futures import ThreadPoolExecutor # 线程池执行器 def task():
print("执行了")
return "玫瑰花"
# 销毁 if __name__ == '__main__':
with ThreadPoolExecutor(max_workers=2) as pool:
res = pool.submit(task)
print(res) # <Future at 0x2539ea97850 state=finished returned str> 这个就是期程对象,可以看到他里面还有当前任务的执行状态。 finished = 执行完了的意思
print(res.result()) # 通过该方法就可以拿到任务的返回结果 # ==== 执行结果 ==== """
执行了
<Future at 0x2539ea97850 state=finished returned str>
玫瑰花
"""
 

  期程对象,也被称为未来对象,是一个非常重要的概念。这里可以记一笔,在Django框架中也有些地方采取了期程对象这样的设定,这是后话,后面再聊。

期程对象如何获取返回结果


  我们尝试着将它的任务数量增多,发现使用期程对象直接获取任务结果会导致阻塞,怎么解决?

import time
import threading
from concurrent.futures import ThreadPoolExecutor # 线程池执行器 def task(x):
print("执行了,这是第%s个任务"%x)
time.sleep(3)
return "玫瑰花"
# 销毁 if __name__ == '__main__':
with ThreadPoolExecutor(max_workers=2) as pool:
for i in range(10):
res = pool.submit(task,i)
print(res.result()) # 每次获取结果的时候都是阻塞,怎么办?这个速率就变得非常的Low逼了。 # ==== 执行结果 ==== """
执行了,这是第0个任务
玫瑰花
执行了,这是第1个任务
玫瑰花
执行了,这是第2个任务
玫瑰花
执行了,这是第3个任务
玫瑰花
执行了,这是第4个任务
玫瑰花
执行了,这是第5个任务
玫瑰花
执行了,这是第6个任务
玫瑰花
执行了,这是第7个任务
玫瑰花
执行了,这是第8个任务
玫瑰花
执行了,这是第9个任务
玫瑰花
"""

  我这里有一个办法,可以值得尝试一下。就是执行器本身有个方法shutdown(wait=True),它会导致当前主线程的阻塞。那么我们就可以这样操作,主程序阻塞住,再将启程对象全部放到一个列表中,当所有任务处理完毕后阻塞通行,这个时候我们再循环这个列表拿出其中的结果。

import time
import threading
from concurrent.futures import ThreadPoolExecutor # 线程池执行器 def task(x):
print("执行了,这是第%s个任务"%x)
time.sleep(3)
return "玫瑰花"
# 销毁 if __name__ == '__main__': res_list = [] # 用于存放所有期程对象 with ThreadPoolExecutor(max_workers=2) as pool:
for i in range(10):
res = pool.submit(task,i)
res_list.append(res) # 将期程对象放入列表 pool.shutdown(wait=True) # 代表必须将所有子线程的任务跑完再继续向下执行主线程。 for i in res_list:
print(i.result()) # ==== 执行结果 ==== """
执行了,这是第0个任务
执行了,这是第1个任务
执行了,这是第2个任务
执行了,这是第3个任务
执行了,这是第4个任务
执行了,这是第5个任务
执行了,这是第6个任务
执行了,这是第7个任务
执行了,这是第8个任务
执行了,这是第9个任务
玫瑰花
玫瑰花
玫瑰花
玫瑰花
玫瑰花
玫瑰花
玫瑰花
玫瑰花
玫瑰花
玫瑰花
"""

  如果你觉得这种方法很赞,我只能送你两个字,太low了。我们注意执行器的submit()方法,这玩意儿是异步提交。异步提交的结果需要用到回调函数来进行调用,我们来看一下它有多牛逼。

回调函数


import time
import threading
from concurrent.futures import ThreadPoolExecutor # 线程池执行器 def task(x):
print("执行了,这是第%s个任务"%x)
time.sleep(3)
return "玫瑰花"
# 销毁 def callback(res): # 必须有一个形参,来接收期程对象
print(res.result()) # 打印结果,即task任务的返回结果 if __name__ == '__main__': with ThreadPoolExecutor(max_workers=2) as pool:
for i in range(10):
res = pool.submit(task,i)
res.add_done_callback(callback) # <--- 增加回调函数,当期程对象中的任务处理状态完毕后将自动调用回调函数 # ==== 执行结果 ==== # 异步提交牛逼不?只要任务返回了我们立马就可以获取到结果进行处理。 """
执行了,这是第0个任务
执行了,这是第1个任务
玫瑰花
玫瑰花
执行了,这是第2个任务
执行了,这是第3个任务
玫瑰花
玫瑰花
执行了,这是第4个任务
执行了,这是第5个任务
玫瑰花
玫瑰花
执行了,这是第6个任务
执行了,这是第7个任务
玫瑰花
玫瑰花
执行了,这是第8个任务
执行了,这是第9个任务
玫瑰花
玫瑰花
"""

扩展:进程池执行器任务数据共享

  当我们使用进程池执行器启动多进程执行任务时,如果想用数据共享,单纯multiprocessing.Queue进程队列并不支持。

import multiprocessing
from concurrent.futures import ProcessPoolExecutor # 进程池执行器 def task_1(q):
q.put("玫瑰花")
print("放完了...") def task_2(q):
print(q.get())
print("取到了") if __name__ == '__main__': q = multiprocessing.Queue() with ProcessPoolExecutor(max_workers=2) as pool:
pool.submit(task_1,q)
pool.submit(task_2,q) # ==== 执行结果 ==== # 阻塞住 """ """

  这个时候我们需要用到multiprocessing中的Manager()中的Queue

from multiprocessing import Manager
from concurrent.futures import ProcessPoolExecutor # 进程池执行器 def task_1(q):
q.put("玫瑰花")
print("放完了...") def task_2(q):
print(q.get())
print("取到了") if __name__ == '__main__': q = Manager().Queue() with ProcessPoolExecutor(max_workers=2) as pool:
pool.submit(task_1,q)
pool.submit(task_2,q) # ==== 执行结果 ==== # 成功 """
放完了...
玫瑰花
取到了
"""

Python线程池与进程池的更多相关文章

  1. Python并发复习4- concurrent.futures模块(线程池和进程池)

    Python标准库为我们提供了threading(多线程模块)和multiprocessing(多进程模块).从Python3.2开始,标准库为我们提供了concurrent.futures模块,它提 ...

  2. python自带的进程池及线程池

    进程池 """ python自带的进程池 """ from multiprocessing import Pool from time im ...

  3. Python并发编程之进程池与线程池

    一.进程池与线程池 python标准模块concurrent.futures(并发未来) 1.concurrent.futures模块是用来创建并行的任务,提供了更高级别的接口,为了异步执行调用 2. ...

  4. python GIL锁、进程池与线程池、同步异步

    一.GIL全局解释器锁 全局解释器锁 在CPython中,全局解释器锁(GIL)是一个互斥锁,它可以防止多个本机线程同时执行Python代码.之所以需要这个锁,主要是因为CPython的内存管理不是线 ...

  5. Python学习之GIL&进程池/线程池

    8.6 GIL锁** Global interpreter Lock 全局解释器锁 实际就是一把解释器级的互斥锁 In CPython, the global interpreter lock, or ...

  6. python之线程池和进程池

    线程池和进程池 一.池的概念 池是用来保证计算机硬件安全的情况下最大限度的利用计算机 它降低了程序的运行效率但是保证了计算机硬件的安全从而让你写的程序能够正常运行 ''' 无论是开设进程也好还是开设线 ...

  7. 协程与concurent.furtrue实现线程池与进程池

    1concurent.furtrue实现线程池与进程池 2协程 1concurent.furtrue实现线程池与进程池 实现进程池 #进程池 from concurrent.futures impor ...

  8. 并发编程:GIL,线程池,进程池,阻塞,非阻塞,同步,异步

    一  GIL(global interpreter lock) GIL中文叫全局解释器锁,我们执行一个文件会产生一个进程,那么我们知道进程不是真正的执行单位,而是资源单位,所以进程中放有解释器(cpy ...

  9. 多进程 multiprocessing 多线程Threading 线程池和进程池concurrent.futures

    multiprocessing.procsess 定义一个函数 def func():pass 在if __name__=="__main__":中实例化 p = process( ...

随机推荐

  1. java实现第六届蓝桥杯密文搜索

    密文搜索 福尔摩斯从X星收到一份资料,全部是小写字母组成. 他的助手提供了另一份资料:许多长度为8的密码列表. 福尔摩斯发现,这些密码是被打乱后隐藏在先前那份资料中的. 请你编写一个程序,从第一份资料 ...

  2. PAT 有几个PAT

    字符串 APPAPT 中包含了两个单词 PAT,其中第一个 PAT 是第 2 位(P),第 4 位(A),第 6 位(T):第二个 PAT 是第 3 位(P),第 4 位(A),第 6 位(T). 现 ...

  3. css背景图片加载失败,页面部分图标无法显示

    1.问题表现:首屏缺失部分图标.点击按钮切换为激活状态时,部分按钮的激活态图标无法显示. 2.问题原因:网络极差,断断续续,点击时添加class:active变为激活态, active.png这张图片 ...

  4. Python简单http服务实现

    1.代码实现 # -*- coding: utf-8 -*-"""Created on Tue Jun 11 18:12:01 2019 @author: wangymd ...

  5. 如何在微信小程序中使用骨架屏

    先上效果图

  6. Memory layout

    Text Segment       Text Segment,通常也被称为代码段. 为了防止 heap 或是 stack 的溢出,text 段常被安排在 heap 或是 stack 之后. Text ...

  7. PIVOT | UNPIVOT_1

    Pivot应用 /* <Microsoft SQL Server 2008 T-SQL Fundamentals (PRO-Developer)> <Microsoft SQL Se ...

  8. shutil模块的使用

    shutil模块 高级的文件,文件夹,压缩包处理模块 shutil.copyfileobj(fsrc,fdst,length) 将文件内容拷贝到另外一个文件中,可以部分.fdst目标length长度( ...

  9. TensorFlow从0到1之回归算法(11)

    回归是数学建模.分类和预测中最古老但功能非常强大的工具之一.回归在工程.物理学.生物学.金融.社会科学等各个领域都有应用,是数据科学家常用的基本工具. 回归通常是机器学习中使用的第一个算法.通过学习因 ...

  10. 拒绝降权!教你用 Python 确保制作的短视频独一无二

    1. 场景 前段时间有人私信我,说自己辛辛苦苦剪辑的短视频,上传到某平台后,由于播放量太大,收到 降权 的通知,直接导致这个账号废掉了! 其实,各大视频平台都有自己的一套鉴别算法,针对视频的二次创作, ...