0/本文提纲
<1>有了多线程threading,为啥还要用multiprocessing
<2>多进程multiprocessing知识梳理
<3>代码实战:单线程,多线程,多进程对比cpu密集型任务的计算速度
<4>最后对池化技术进行解释
1/有了多线程threading,为啥还要用multiprocessing
GIL: 全局解释器锁
GIL在IO操作时会主动释放 (IO密集型任务)
GIL会根据执行的字节码行数以及时间片释放GIL(CPU密集型任务)
multiprocessing模块是python为了解决GIL缺陷而引入的一个模块,
原理是用多进程在多cpu(核心)上并行执行。

从上图中可以知道:
对于IO密集型任务,使用python多线程,虽然因为GIL的存在导致任务是并发执行的(不是并行执行),
但是由于IO的存在,所有多线程依然可以加速运行。
比如thread1先执行,等thread1遇到了IO,这个时候thread1释放GIL,
然后thread2获得GIL,然后开始执行,同时thread1正在IO,
所以这样情况下是可以加速运行的。
但是对于CPU密集型任务,由于IO很少甚至没有,
如果使用多线程,则刚run 100 ticks的时候,线程会自动切换,
这样反而变成了负担,减慢了执行速度。
所以cpu密集型任务,不适合使用多线程。
2/多进程multiprocessing知识梳理
python提供的threading模块和multiprocessing模块很像,语法几乎完全一样,
这是python官方有意而为之的,
目的是让开发这可以方便无缝的迁移。

3/代码实战:单线程,多线程,多进程对比cpu密集型任务的计算速度

import time
import math
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
PRIMES = [56687541674151654] * 100 # 大小为100的列表,每个元素都很大
# 判断一个数字是否为素数
def func_is_prime(n):
if n < 2:
return False
if n == 2:
return True
if n % 2 == 0:
return False
sqrt_n = int( math,floot(math.sqrt(n)) )
for i in range(3,sqrt_n+1,2):
if n % i == 0:
return False
return True
# 单线程
def single_thread():
for i in PRIMES:
func_is_prime(i)
# 多线程
def multi_thread():
with ThreadPoolExecutor as pool:
pool.map(func_is_prime,PRIMES)
# 多进程
def multi_process():
with ProcessPoolExecutor as pool:
pool.map(func_is_prime,PRIMES)
if __name__ == "__main__":
start_time = time.time()
single_thread()
end_time = time.time()
print('单线程所用时间为:',end_time - start-time)
start_time = time.time()
multi_thread()
end_time = time.time()
print('多线程所用时间为:',end_time - start-time)
start_time = time.time()
multi_process()
end_time = time.time()
print('多进程所用时间为:',end_time - start-time)
4/最后对池化技术进行解释
<1>基类Executor
Executor类是ThreadPoolExecutor和ProcessPoolExecutor的基类。
ThreadPoolExecutor和ProcessPoolExecutor分别对threading和multiprocessing进行了高级抽象,暴露出简单的统一接口。
它为我们提供了如下方法:
(1)submit(fn,*args,**kwargs)
提交任务。以fn(*args **kwargs) 方式执行并返回 Future 对像。
fn: 函数地址。
*args:位置参数。
**kwargs:关键字参数
(2)map(func,*iterables,timeout=None,chunksize=1)
1)func: 函数地址。
2)iterables: 一个可迭代对象,以迭代的方式将参数传递给函数。
3)timeout: 这个参数没弄明白,如果是None等待所有进程结束。
4)chunksize: 使用ProcessPoolExecutor时,
这个方法会将iterables分割任务块,并作为独立的任务提交到执行池中。
这些块的数量可以由chunksize指定设置。
对很长的迭代器来说,重新设置chunksize值会比默认值1能显著地提高性能。
chunksize对ThreadPoolExecutor没有效果。
5)shutdown(wait=True):如果为True会等待线程池或进程池执行完成后释放正在使用的资源。
如果wait=False,将立即返回,所有待执行的期程完成执行后会释放已分配的资源。
不管wait的值是什么,整个Python程序将等到所有待执行的期程完成执行后才退出。
<2>线程池对象
ThreadPoolExecutor是Executor的子类,下面介绍ThreadPoolExecutor的参数。
class concurrent.futures.ThreadPoolExecutor(
max_workers=None,
thread_name_prefix='',
initializer=None,
initargs=()):
max_workers:线程池的数量。
thread_name_prefix:线程名前缀。默认线程名ThreadPoolExecutor-线程数。
initializer:一个函数或方法,在启用线程前会调用这个函数(给线程池添加额外任务)。
initargs 以元祖的方式给initializer中的函数传递参数。
这里需要说明的是除了max_workers这个参数外,
其它三个参数基本很少用。
max_workers很好理解就是线程池的数量,就是线程池里最多可以放多少个线程。
<3>进程池对象
ProcessPoolExecutor类也是Executor的子类,
下面是ProcessPoolExecutor参数介绍:
class concurrent.futures.ProcessPoolExecutor(
max_workers=None,
mp_context=None,
initializer=None,
initargs=())
max_workers:进程数,及进程池子里最多的进程的数量,及我们打算开启多少个进程。
如果max_workers为None或未给出(默认),它将默认为机器的处理器个数(及总cpu核心数)。
如果max_workers小于等于0,则将引发 ValueError。
在 Windows上,max_workers必须小于等于 61,否则将引发ValueError。
如果max_workers为None,则所选择的默认最多为61,即使存在更多处理器。
mp_context:可以是一个多进程上下文或是 None。它将被用来启动工作进程。
如果 mp_context 为 None 或未给出,将使用默认的多进程上下文。
initializer:一个函数或方法,在启用线程前会调用这个函数。
initargs :以元组的方式给initializer中的函数传递参数。
关于说initializer和initargs 与ThreadPoolExecutor 类似这里不多说了。

从上图可以知道,在对类ProcessPoolExecutor进行实例化对象的时候,
初始化一个数据,作为进程池中开启的进程的数量。
如果不给这个数字,则默认为服务器的cpu的总核心数
shutdown()函数,等价于close+join,关闭进程池,
另外堵塞主进程,及等待进程池中的所有任务都执行完毕了,再执行主进程。