Python进程池与进程锁之语法学习


Posted in Python onApril 11, 2022

NICE!大家好,在上一章节,我们学习了 multiprocessing 模块 的关于进程的创建与进场常用的方法的相关知识。 通过在一个主进程下创建多个子进程可以帮助我们加速程序的运行,并且提高工作效率。不过上一章节文末我们也说过进程的问题,由于每一个进程都会消耗 CPU 与 内存 资源,这样就不能无限的创建进程的问题,因为会造成内存不足或者死机的情况。

为了解决这个问题我们可以使用多线程来替代,或者使用我们今天要学习的内容 —> 进程池。不仅如此,我们在上一章节也说了另一个问题,多进程在同时修改一个文件的时候可能会存在问题,解决的方法就是给这一个文件进行 上锁 。今天我们就来学习一下 进程池与进程锁 ,看看它们都能帮助我们怎样解决问题。

进程池

什么是进程池

上一章节关于进程的问题我们提到过,进程创建太多的情况下就会对资源消耗过大。为了避免出现这种情况,我们就需要固定进程的数量,这时候就需要进程池的帮助。

我们可以认为进程池就是一个池子,在这个池子里提前创建好一定数量的进程。见下图:

Python进程池与进程锁之语法学习

比如这个红色矩形阵列就代表一个进程池子,在这个池子中有6个进程。这6个进程会伴随进程池一起被创建,不仅如此,我们在学习面向对象的生命周期的时候曾经说过,每个实例化对象在使用完成之后都会被内存管家回收。

我们的进程也会伴随着创建与关闭的过程而被内存管家回收,每一个都是如此,创建于关闭进程的过程也会消耗一定的性能。而进程池中的进程当被创建之后就不会被关闭,可以一直被重复使用,从而避免了创建于关闭的资源消耗,也避免了创建于关闭的反复操作提高了效率。

当然,当我们执行完程序进程池关闭的时候,进程也随之关闭。

当我们有任务需要被执行的时候,会判断当前的进程池当中有没有空闲的进程(所谓空闲的进程其实就是进程池中没有执行任务的进程)。有进程处于空闲状态的情况下,任务会找到进程执行该任务。如果当前进程池中的进程都处于非空闲状态,则任务就会进入等待状态,直到进程池中有进程处于空闲状态才会进出进程池从而执行该任务。

这就是进程池的作用。

进程池的创建模块 - multiprocessing

创建进程池函数 - Pool

函数名 介绍 参数 返回值
Pool 进程池的创建 Processcount 进程池对象

Pool功能介绍:通过调用 "multiprocessing" 模块的 "Pool" 函数来帮助我们创建 "进程池对象" ,它有一个参数 "Processcount" (一个整数),代表我们这个进程池中创建几个进程。

进程池的常用方法

当创建了进程池对象之后,我们要对它进程操作,让我们来看一下都有哪些常用方法(函数)。

函数名 介绍 参数 返回值
apply_async 任务加入进程池(异步) func,args
close 关闭进程池
join 等待进程池任务结束
  • apply_async 函数:它的功能是将任务加入到进程池中,并且是通过异步实现的。异步 这个知识我们还没有学习,先不用关心它到底是什么意思。它有两个参数:func 与 agrs , func 是加入进程池中工作的函数;args 是一个元组,代表着签一个函数的参数,这和我们创建并使用一个进程是完全一致的。
  • close 函数:当我们使用完进程池之后,通过调用 close 函数可以关闭进程池。它没有任何的参数,也没有任何的返回值。
  • join 函数:它和我们上一章节学习的 创建进程的 join 函数中方法是一致的。只有进程池中的任务全部执行完毕之后,才会执行后续的任务。不过一般它会伴随着进程池的关闭(close 函数)才会使用。

apply_async 函数演示案例

接下里我们在 Pycharm 中创建一个脚本,练习一下关于进程池的使用方法。

  • 定义一个函数,打印输出该函数 每次被执行的次数 与 该次数的进程号
  • 定义进程池的数量,每一次的执行进程数量最多为该进程池设定的进程数

示例代码如下:

# coding:utf-8


import os
import time
import multiprocessing

def work(count):    # 定义一个 work 函数,打印输出 每次执行的次数 与 该次数的进程号
    print('\'work\' 函数 第 {} 次执行,进程号为 {}'.format(count, os.getpid()))
    time.sleep(3)
    # print('********')


if __name__ == '__main__':
    pool = multiprocessing.Pool(3)      # 定义进程池的进程数量,同一时间每次执行最多3个进程
    for i in range(21):
        pool.apply_async(func=work, args=(i,))      # 传入的参数是元组,因为我们只有一个 i 参数,所以我们要写成 args=(i,)

    time.sleep(15)      # 这里的休眠时间是必须要加上的,否则我们的进程池还未运行,主进程就已经运行结束,对应的进程池也会关闭。

运行结果如下:

Python进程池与进程锁之语法学习

从上图中我们可以看到每一次都是一次性运行三个进程,每一个进程的进程号是不一样的,但仔细看会发现存在相同的进程号,这说明进程池的进程号在被重复利用。这证明我们上文介绍的内容,进程池中的进程不会被关闭,可以反复使用。

而且我们还可以看到每隔3秒都会执行3个进程,原因是我们的进程池中只有3个进程;虽然我们的 for 循环 中有 21 个任务,work 函数会被执行21次,但是由于我们的进程池中只有3个进程。所以当执行了3个任务之后(休眠3秒),后面的任务等待进程池中的进程处于空闲状态之后才会继续执行。

同样的,进程号在顺序上回出现一定的区别,原因是因为我们使用的是一种 异步 的方法(异步即非同步)。这就导致 work 函数 一起执行的三个任务会被打乱顺序,这也是为什么我们的进程号出现顺序不一致的原因。(更多的异步知识我们会在异步的章节进行详细介绍)

进程池的原理: 上述脚本的案例证实了我们进程池关于进程的限制,只有当我们进程池中的进程处于空闲状态的时候才会将进程池外等待的任务扔到进程池中工作。

close 函数与 join 函数 演示

在上文的脚本中, 我们使用 time.sleep(15) 帮助我们将主进程阻塞15秒钟再次退出,所以给了我们进程池足够的时间完成我们的 work() 函数的循环任务。

如果没有 time.sleep(15) 这句话又怎么办呢,其实这里就可以使用进程的 join 函数了。不过上文我们也提到过,进程的 join() 函数一般都会伴随进程池的关闭(close 函数)来使用。接下来,我们就将上文脚本中的 time.sleep(15) 替换成 join() 函数试一下。

示例代码如下:

# coding:utf-8


import os
import time
import multiprocessing

def work(count):    # 定义一个 work 函数,打印输出 每次执行的次数 与 该次数的进程号
    print('\'work\' 函数 第 {} 次执行,进程号为 {}'.format(count, os.getpid()))
    time.sleep(3)
    # print('********')


if __name__ == '__main__':
    pool = multiprocessing.Pool(3)      # 定义进程池的进程数量,同一时间每次执行最多3个进程
    for i in range(21):
        pool.apply_async(func=work, args=(i,))      # 传入的参数是元组,因为我们只有一个 i 参数,所以我们要写成 args=(i,)

    # time.sleep(15) 
    pool.close()
    pool.join()

运行结果如下:

Python进程池与进程锁之语法学习

从上面的动图我们可以看出,work() 函数的任务与进程池中的进程与使用 time.sleep(15)的运行结果一致。

PS:如果我们的主进程会一直执行,不会退出。那么我们并不需要添加 close() 与 join() 函数 ,可以让进程池一直启动着,直到有任务进来就会执行。

在后面学习 WEB 开发之后,不退出主进程进行工作是家常便饭。还有一些需要长期执行的任务也不会关闭,但要是只有一次性执行的脚本,就需要添加 close() 与 join() 函数 来保证进程池的任务全部完成之后主进程再退出。当然,如果主进程关闭了,就不会再接受新的任务了,也就代表了进程池的终结。

接下来再看一个例子,在 work 函数 中加入一个 return。

这里大家可能会有一个疑问,在上一章节针对进程的知识点明明说的是 进程无法获取返回值,那么这里的 work() 函数增加的 return 又有什么意义呢?

其实不然,在我们的使用进程池的 apply_async 方法时,是通过异步的方式实现的,而异步是可以获取返回值的。针对上述脚本,我们在 for循环中针对每一个异步 apply_async 添加一个变量名,从而获取返回值。

示例代码如下:

# coding:utf-8


import os
import time
import multiprocessing

def work(count):    # 定义一个 work 函数,打印输出 每次执行的次数 与 该次数的进程号
    print('\'work\' 函数 第 {} 次执行,进程号为 {}'.format(count, os.getpid()))
    time.sleep(3)
    return '\'work\' 函数 result 返回值为:{}, 进程ID为:{}'.format(count, os.getpid())


if __name__ == '__main__':
    pool = multiprocessing.Pool(3)      # 定义进程池的进程数量,同一时间每次执行最多3个进程
    results = []
    for i in range(21):
        result = pool.apply_async(func=work, args=(i,))      # 传入的参数是元组,因为我们只有一个 i 参数,所以我们要写成 args=(i,)
        results.append(result)

    for result in results:
        print(result.get())     # 可以通过这个方式返回 apply_async 的返回值,
                                # 通过这种方式也不再需要 使用 close()、join() 函数就可以正常执行。

    # time.sleep(15)      # 这里的休眠时间是必须要加上的,否则我们的进程池还未运行,主进程就已经运行结束,对应的进程池也会关闭。
    # pool.close()
    # pool.join()

运行结果如下:

Python进程池与进程锁之语法学习

从运行结果可以看出,首先 work() 函数被线程池的线程执行了一遍,当第一组任务执行完毕紧接着执行第二次线程池任务的时候,打印输出了 apply_async 的返回值,证明返回值被成功的返回了。然后继续下一组的任务…

这些都是主要依赖于 异步 ,关于 异步 的更多知识会在 异步 的章节进行详细的介绍。

进程锁

进程锁的概念

锁:大家都知道,我们可以给一个大门上锁。

结合这个场景来举一个例子:比如现在有多个进程同时冲向一个 "大门" ,当前门内是没有 "人"的(其实就是进程),锁也没有锁上。当有一个进程进去之后并且把 “门” 锁上了,这时候门外的那些进程是进不来的。在门内的 “人” ,可以在 “门” 内做任何事情且不会被干扰。当它出来之后,会解开门锁。这时候又有一个 “人” 进去了门内,并且重复这样的操作,这就是 进程锁。它可以让锁后面的工作只能被一个任务来处理,只有它解锁之后下一个任务才会进入,这就是 “锁” 的概念。

而 进程锁 就是仅针对于 进程 有效的锁,当进程的任务开始之后,就会被上一把 “锁”;与之对应的是 线程锁 ,它们的原理几乎是一样的。

进程锁的加锁与解锁

进程锁的使用方法:

通过 multiprocessing 导入 Manager 类

from multiprocessing import Manager

然后实例化 Manager

manager = Manager()

再然后通过实例化后的 manager 调用 它的 Lock() 函数

lock = manager.Lock()

接下来,就需要操作这个 lock 对象的函数

函数名 介绍 参数 返回值
acquire 上锁
release 解锁(开锁)

代码示例如下:

# coding:utf-8


import os
import time
import multiprocessing


def work(count, lock):    # 定义一个 work 函数,打印输出 每次执行的次数 与 该次数的进程号,增加线程锁。
    lock.acquire()        # 上锁
    print('\'work\' 函数 第 {} 次执行,进程号为 {}'.format(count, os.getpid()))
    time.sleep(3)
    lock.release()        # 解锁
    return '\'work\' 函数 result 返回值为:{}, 进程ID为:{}'.format(count, os.getpid())


if __name__ == '__main__':
    pool = multiprocessing.Pool(3)      # 定义进程池的进程数量,同一时间每次执行最多3个进程
    manager = multiprocessing.Manager()
    lock = manager.Lock()
    results = []
    for i in range(21):
        result = pool.apply_async(func=work, args=(i, lock))      # 传入的参数是元组,因为我们只有一个 i 参数,所以我们要写成 args=(i,)
        # results.append(result)


    # time.sleep(15)      # 这里的休眠时间是必须要加上的,否则我们的进程池还未运行,主进程就已经运行结束,对应的进程池也会关闭。
    pool.close()
    pool.join()

执行结果如下:

Python进程池与进程锁之语法学习

从上图中,可以看到每一次只有一个任务会被执行。由于每一个进程会被阻塞 3秒钟,所以我们的进程执行的非常慢。这是因为每一个进程进入到 work() 函数中,都会执行 上锁、阻塞3秒、解锁 的过程,这样就完成了一个进程的工作。下一个进程任务开始,重复这个过程… 这就是 进程锁的概念。

其实进程锁还有很多种方法,在 multiprocessing 中有一个直接使用的锁,就是 ``from multiprocessing import Lock。这个Lock的锁使用和我们刚刚介绍的Manager` 的锁的使用有所区别。(这里不做详细介绍,感兴趣的话可以自行拓展一下。)

锁 的使用可以让我们对某个任务 在同一时间只能对一个进程进行开发,但是 锁也不可以乱用 。因为如果某些原因造成 锁没有正常解开 ,就会造成死锁的现象,这样就无法再进行操作了。

因为 锁如果解不开 ,后面的任务也就没有办法继续执行任务,所以使用锁一定要谨慎。

OKK,今天我们学习了进程池与锁的使用方法,通过学习这两个知识点可以帮助我们解决进程的一些弊端,但它们自身的使用也要注意一些事项。在不同的场景使用的效果也不尽相同,而 锁的使用 则更需要注意。

Python 相关文章推荐
一则python3的简单爬虫代码
May 26 Python
Python3基础之条件与循环控制实例解析
Aug 13 Python
用Python进行基础的函数式编程的教程
Mar 31 Python
Python中字典的基本知识初步介绍
May 21 Python
Python本地与全局命名空间用法实例
Jun 16 Python
Python3使用PyQt5制作简单的画板/手写板实例
Oct 19 Python
Python中str.join()简单用法示例
Mar 20 Python
python 自定义对象的打印方法
Jan 12 Python
python 利用pywifi模块实现连接网络破解wifi密码实时监控网络
Sep 16 Python
django实现日志按日期分割
May 21 Python
Python 发送邮件方法总结
Aug 10 Python
pycharm2020.1.2永久破解激活教程,实测有效
Oct 29 Python
Python进程间的通信之语法学习
Python+Matplotlib图像上指定坐标的位置添加文本标签与注释
浅析Python OpenCV三种滤镜效果
实战Python爬虫爬取酷我音乐
用PYTHON去计算88键钢琴的琴键频率和音高
python图像处理 PIL Image操作实例
Apr 09 #Python
Python Pytorch查询图像的特征从集合或数据库中查找图像
You might like
PHP mb_convert_encoding 获取字符串编码类型实现代码
2009/04/26 PHP
php PDO中文乱码解决办法
2009/07/20 PHP
如何用javascript去掉字符串里的所有空格
2007/02/08 Javascript
Javascript 跨域访问解决方案
2009/02/14 Javascript
javascript中RegExp保留小数点后几位数的方法分享
2013/08/13 Javascript
form.submit()不能提交表单的错误原因及解决方法
2014/10/13 Javascript
jquery隔行换色效果实现方法
2015/01/15 Javascript
使用Node.js实现HTTP 206内容分片的教程
2015/06/23 Javascript
js判断浏览器类型及设备(移动页面开发)
2015/07/30 Javascript
JS面向对象编程详解
2016/03/06 Javascript
JavaScript和jquery获取父级元素、子级元素、兄弟元素的方法
2016/06/05 Javascript
js倒计时显示实例
2016/12/11 Javascript
javascript工厂模式和构造函数模式创建对象方法解析
2016/12/30 Javascript
ionic App问题总结系列之ionic点击系统返回键退出App
2017/08/19 Javascript
纯js实现隔行变色效果
2017/11/29 Javascript
vue-cli axios请求方式及跨域处理问题
2018/03/28 Javascript
微信小程序实现评论功能
2018/11/28 Javascript
Vue实现星级评价效果实例详解
2019/12/30 Javascript
[01:03]DOTA2新的征程 你的脚印值得踏上
2014/08/13 DOTA
windows系统中python使用rar命令压缩多个文件夹示例
2014/05/06 Python
Python数据结构之双向链表的定义与使用方法示例
2018/01/16 Python
Python常见MongoDB数据库操作实例总结
2018/07/24 Python
python集合是否可变总结
2019/06/20 Python
使用virtualenv创建Python环境及PyQT5环境配置的方法
2019/09/10 Python
python连接打印机实现打印文档、图片、pdf文件等功能
2020/02/07 Python
Python解释器及PyCharm工具安装过程
2020/02/26 Python
.img/.hdr格式转.nii格式的操作
2020/07/01 Python
戴森台湾线上商城:Dyson Taiwan
2018/05/21 全球购物
C++如何引用一个已经定义过的全局变量
2014/08/25 面试题
旅游管理专业大学生职业规划书
2014/02/27 职场文书
大型演出策划方案
2014/05/28 职场文书
企业标语口号
2014/06/10 职场文书
2014年语文教学工作总结
2014/12/17 职场文书
学生通报表扬范文
2015/05/04 职场文书
vue实现水波涟漪效果的点击反馈指令
2021/05/31 Vue.js
Python实现老照片修复之上色小技巧
2021/10/16 Python