Python进程间的通信之语法学习


Posted in Python onApril 11, 2022

什么是进程的通信

这里举一个例子接介绍通信的机制:通信 一词大家并不陌生,比如一个人要给他的女友打电话。当建立了通话之后,在这个通话的过程中就是建立了一条隐形的 队列 (记住这个词)。此时这个人就会通过对话的方式不停的将信息告诉女友,而这个人的女友也是在倾听着。(嗯…我个人觉得大部分情况下可能是反着来的)。

这里可以将他们两个人比作是两个进程,"这个人"的进程需要将信息发送给"女友"的进程,就需要一个队列的帮助。而女友需要不停的接收队列的信息,可以做一些其他的事情,所以两个进程之间的通信主要依赖于队列。

这个队列可以支持发送消息与接收消息,“这个人"负责发送消息,反之"女友” 负责的是接收消息。

既然队列才是重点,那么来看一下队列要如何创建。

队列的创建 - multiprocessing

依然使用 multiprocessing 模块,调用该模块的 Queue 函数来实现队列的创建。

函数名 介绍 参数 返回值
Queue 队列的创建 mac_count 队列对象

Queue 函数功能介绍:调用 Queue 可以创建队列;它有一个参数 mac_count 代表队列最大可以创建多少信息,如果不传默认是无限长度。实例化一个队列对象之后,需要操作这个队列的对象进行放入与取出数据。

进程之间通信的方法

函数名 介绍 参数 返回值
put 将消息放入队列 message
get 获取队列消息 str

put 函数功能介绍:将数据传入。它有一个参数 message ,是一个字符串类型。

get 函数功能介绍:用来接收队列中的数据。(其实这里就是一个常用的json场景,有很多的数据传输都是 字符串 的,队列的插入与获取就是使用的字符串,所以 json 就非常适用这个场景。)

接下来就来练习一下 队列的使用 。

进程间的通信 - 队列演示案例

代码示例如下:

# coding:utf-8


import json
import multiprocessing


class Work(object):     # 定义一个 Work 类
    def __init__(self, queue):      # 构造函数传入一个 '队列对象' --> queue
            self.queue = queue

    def send(self, message):        # 定义一个 send(发送) 函数,传入 message
                                    # [这里有个隐藏的bug,就是只判断了传入的是否字符串类型;如果传入的是函数、类、集合等依然会报错]
        if not isinstance(message, str):    # 判断传入的 message 是否为字符串,若不是,则进行 json 序列化
            message = json.dumps(message)
        self.queue.put(message)     # 利用 queue 的队列实例化对象将 message 发送出去

    def receive(self):      # 定义一个 receive(接收) 函数,不需传入参数,但是因为接收是一个源源不断的过程,所以需要使用 while 循环
        while 1:
            result = self.queue.get()   # 获取 '队列对象' --> queue 传入的message
                                        # 由于我们接收的 message 可能不是一个字符串,所以要进程异常的捕获
            try:                        # 如果传入的 message 符合 JSON 格式将赋值给 res ;若不符合,则直接使用 result 赋值 res
                res = json.loads(result)
            except:
                res = result
            print('接收到的信息为:{}'.format(res))


if __name__ == '__main__':
    queue = multiprocessing.Queue()
    work = Work(queue)
    send = multiprocessing.Process(target=work.send, args=({'message': '这是一条测试的消息'},))
    receive = multiprocessing.Process(target=work.receive)

    send.start()
    receive.start()

使用队列建立进程间通信遇到的异常

但是这里会出现一个 报错,如下图:

报错截图示例如下:

Python进程间的通信之语法学习

这里的报错提示是 文件没有被发现的意思 。其实这里是我们使用 队列做 put() 和 get()的时候 有一把无形的锁加了上去,就是上图中圈中的 .SemLock 。我们不需要去关心造成这个错误的具体原因,要解决这个问题其实也很简单。

FileNotFoundError: [Errno 2] No such file or directory 异常的解决

我们只需要给 send 或者 receive 其中一个子进程添加 join 阻塞进程即可,理论上如此。但是我们的 receive子进程是一个 while循环,它会一直执行,所以只需要给 send 子进程加上一个 join 即可。

解决示意图如下:

Python进程间的通信之语法学习

PS:虽然解决了报错问题,但是程序没有正常退出。

实际上由于我们的 receive 进程是个 while循环,并不知道要处理到什么时候,没有办法立刻终止。所以我们需要在 receive 进程 使用 terminate() 函数终结接收端。

运行结果如下:

Python进程间的通信之语法学习

批量给 send 函数加入数据

新建一个函数,写入 for循环 模拟批量添加要发送的消息

然后再给这个模拟批量发送数据的函数添加一个线程。

示例代码如下:

# coding:utf-8


import json
import time
import multiprocessing


class Work(object):     # 定义一个 Work 类
    def __init__(self, queue):      # 构造函数传入一个 '队列对象' --> queue
            self.queue = queue

    def send(self, message):        # 定义一个 send(发送) 函数,传入 message
                                    # [这里有个隐藏的bug,就是只判断了传入的是否字符串类型;如果传入的是函数、类、集合等依然会报错]
        if not isinstance(message, str):    # 判断传入的 message 是否为字符串,若不是,则进行 json 序列化
            message = json.dumps(message)
        self.queue.put(message)     # 利用 queue 的队列实例化对象将 message 发送出去


    def send_all(self):             # 定义一个 send_all(发送)函数,然后通过for循环模拟批量发送的 message
        for i in range(20):
            self.queue.put('第 {} 次循环,发送的消息为:{}'.format(i, i))
            time.sleep(1)



    def receive(self):      # 定义一个 receive(接收) 函数,不需传入参数,但是因为接收是一个源源不断的过程,所以需要使用 while 循环
        while 1:
            result = self.queue.get()   # 获取 '队列对象' --> queue 传入的message
                                        # 由于我们接收的 message 可能不是一个字符串,所以要进程异常的捕获
            try:                        # 如果传入的 message 符合 JSON 格式将赋值给 res ;若不符合,则直接使用 result 赋值 res
                res = json.loads(result)
            except:
                res = result
            print('接收到的信息为:{}'.format(res))


if __name__ == '__main__':
    queue = multiprocessing.Queue()
    work = Work(queue)
    send = multiprocessing.Process(target=work.send, args=({'message': '这是一条测试的消息'},))
    receive = multiprocessing.Process(target=work.receive)
    send_all = multiprocessing.Process(target=work.send_all,)


    send_all.start()    # 这里因为 send 只执行了1次,然后就结束了。而 send_all 却要循环20次,它的执行时间是最长的,信息也是发送的最多的
    send.start()
    receive.start()

    # send.join()       # 使用 send 的阻塞会造成 send_all 循环还未结束 ,receive.terminate() 函数接收端就会终结。
    send_all.join()     # 所以我们只需要阻塞最长使用率的进程就可以了
    receive.terminate()

运行结果如下:

Python进程间的通信之语法学习

从上图中我们可以看到 send 与 send_all 两个进程都可以通过 queue这个实例化的 Queue 对象发送消息,同样的 receive接收函数也会将两个进程传入的 message 打印输出出来。

小节

该章节我们通过队列的方式实现了进程间通信的方法,并且了解了队列的使用方法。一个队列中,有一端(这里我们演示的是 send端)通过 put方法实现添加相关的信息,另一端使用 get 方法获取相关的信息;两个进程相互配合达到一个进程通信的效果。

其实进程之间的通信不仅仅只有队列这一种方式,感兴趣的话还可以通过 管道、信号量、共享内存的方式来实现。可以自行拓展一下。

进程间通信的其他方式 - 补充

python提供了多种进程通信的方式,包括信号,管道,消息队列,信号量,共享内存,socket等

主要Queue和Pipe这两种方式,Queue用于多个进程间实现通信,Pipe是两个进程的通信。

1.管道:分为匿名管道和命名管道

匿名管道:在内核中申请一块固定大小的缓冲区,程序拥有写入和读取的权利,一般使用fock函数实现父子进程的通信

命名管道:在内存中申请一块固定大小的缓冲区,程序拥有写入和读取的权利,没有血缘关系的进程也可以进程间通信

特点:面向字节流;生命周期随内核;自带同步互斥机制;半双工,单向通信,两个管道实现双向通信

2.消息队列:在内核中创建一个队列,队列中每个元素是一个数据报,不同的进程可以通过句柄去访问这个队列。消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法。每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型。消息队列也有管道一样的不足,就是每个消息的最大长度是有上限的,每个消息队列的总的字节数是有上限的,系统上消息队列的总数也有一个上限

特点:消息队列可以被认为是一个全局的一个链表,链表节点中存放着数据报的类型和内容,有消息队列的标识符进行标记;消息队列允许一个或多个进程写入或读取消息;消息队列的生命周期随内核;消息队列可实现双向通信

3.信号量:在内核中创建一个信号量集合(本质上是数组),数组的元素(信号量)都是1,使用P操作进行-1,使用V操作+1

P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该程序的执行

V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1

PV操作用于同一个进程,实现互斥;PV操作用于不同进程,实现同步

功能:对临界资源进行保护

4.共享内存:将同一块物理内存一块映射到不同的进程的虚拟地址空间中,实现不同进程间对同一资源的共享。共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式

特点:不同从用户态到内核态的频繁切换和拷贝数据,直接从内存中读取就可以;共享内存是临界资源,所以需要操作时必须要保证原子性。使用信号量或者互斥锁都可以.

Python 相关文章推荐
Python的Django中django-userena组件的简单使用教程
May 30 Python
python技能之数据导出excel的实例代码
Aug 11 Python
Python学习小技巧总结
Jun 10 Python
Django的Modelforms用法简介
Jul 27 Python
python自动化UI工具发送QQ消息的实例
Aug 27 Python
Python配置文件处理的方法教程
Aug 29 Python
python文件绝对路径写法介绍(windows)
Dec 25 Python
python统计字符串中字母出现次数代码实例
Mar 02 Python
python GUI库图形界面开发之PyQt5信号与槽多窗口数据传递详细使用方法与实例
Mar 08 Python
Python新手学习raise用法
Jun 03 Python
使用scrapy ImagesPipeline爬取图片资源的示例代码
Sep 28 Python
使用tkinter实现三子棋游戏
Feb 25 Python
Python+Matplotlib图像上指定坐标的位置添加文本标签与注释
浅析Python OpenCV三种滤镜效果
实战Python爬虫爬取酷我音乐
用PYTHON去计算88键钢琴的琴键频率和音高
python图像处理 PIL Image操作实例
Apr 09 #Python
Python Pytorch查询图像的特征从集合或数据库中查找图像
Python实现科学占卜 让视频自动打码
You might like
咖啡界又出新概念,无需咖啡豆的分子咖啡
2021/03/03 咖啡文化
用PHP调用数据库的存贮过程!
2006/10/09 PHP
php从数组中随机选择若干不重复元素的方法
2015/03/14 PHP
php将一维数组转换为每3个连续值组成的二维数组
2016/05/06 PHP
PHP获取文件扩展名的方法实例总结
2017/06/10 PHP
PHP创建XML接口示例
2019/07/04 PHP
javascript 对象比较实现代码
2009/04/27 Javascript
基于jquery的设置页面文本框 只能输入数字的实现代码
2011/04/19 Javascript
NodeJS学习笔记之(Url,QueryString,Path)模块
2015/01/13 NodeJs
JavaScript通过元素索引号删除数组中对应元素的方法
2015/03/18 Javascript
JavaScript实现的类字典插入或更新方法实例
2015/07/10 Javascript
深入理解JS中的substr和substring
2016/04/26 Javascript
详解javascript中对数据格式化的思考
2017/01/23 Javascript
jQuery读取XML文件的方法示例
2017/02/03 Javascript
手把手教你使用vue-cli脚手架(图文解析)
2017/11/08 Javascript
原生JS实现DOM加载完成马上执行JS代码的方法
2018/09/07 Javascript
通过cordova将vue项目打包为webapp的方法
2019/02/02 Javascript
Emberjs 通过 axios 下载文件的方法
2019/09/03 Javascript
解决antd 下拉框 input [defaultValue] 的值的问题
2020/10/31 Javascript
[01:14:35]DOTA2上海特级锦标赛B组资格赛#1 Alliance VS Fnatic第一局
2016/02/26 DOTA
[35:34]Liquid vs Winstrike 2018国际邀请赛小组赛BO2 第一场 8.18
2018/08/19 DOTA
python实现扫描ip地址的小程序
2019/04/16 Python
Django认证系统实现的web页面实现代码
2019/08/12 Python
关于Tensorflow 模型持久化详解
2020/02/12 Python
python 双循环遍历list 变量判断代码
2020/05/04 Python
Python 找出出现次数超过数组长度一半的元素实例
2020/05/11 Python
Molton Brown美国官网:奢华美容、香水、沐浴和身体护理
2020/09/02 全球购物
党的群众路线教育实践活动总结报告
2014/04/28 职场文书
幼儿园区域活动总结
2014/05/08 职场文书
2015年党支部书记工作总结
2015/05/21 职场文书
中秋节晚会开场白
2015/05/29 职场文书
堂吉诃德读书笔记
2015/06/30 职场文书
幼儿园亲子活动感想
2015/08/07 职场文书
《家世》读后感:看家训的力量
2019/12/30 职场文书
深入理解Vue的数据响应式
2021/05/15 Vue.js
Python中可变和不可变对象的深入讲解
2021/08/02 Python