[Python学习日记-85] 并发编程之多进程 —— Process 类、join 方法、僵尸进程与孤儿进程

[Python学习日记-85] 并发编程之多进程 —— Process 类、join 方法、僵尸进程与孤儿进程

简介

multiprocessing 模块

Process 类

僵尸进程与孤儿进程

简介

        在前面的进程理论的介绍当中我们已经介绍了进程的概念、并发与并行的区别以及进程并发的实现理论,这些都是比较偏向于理论的。本篇我们将进入实际敲代码的阶段,看看在 Python 当中应该如何创建进程,以及使用进程时有什么需要注意的地方。

multiprocessing 模块

        现在这个时代基本上已经很少看到单核的 CPU 了,基本上都是多核的 CPU,而在 Python 中的多线程并无法利用多核的优势,只能一个核分时的去多线程操作,如果想要充分的利用多核的资源,这个时候就是 multiprocessing 模块登场的时候了。

        multiprocessing 模块是 Python 中用于支持多进程编程的标准库,该模块与多线程模块 threading 的编程接口类似。我们可以利用该 multiprocessing 模块来开启子进程,并在子进程中执行我们定制的任务(函数之类的)。

        multiprocessing 模块支持子进程的创建;提供了在多个进程之间进行通信、协同和共享数据的工具;使得可以利用多核处理器来同时执行多个任务,即可以执行不同形式的同步形式,从而可以更方便地进行并行编程,充分利用计算资源,提高程序的效率和性能。

        此外还提供了 Process、Pool、Queue、Pipe、Lock 等组件。

  • Process 类:用于表示一个进程,可以通过创建该类的实例来启动一个新的进程。
  • Pool 类:提供了一种将任务分配给多个进程来并行执行的方式。
  • Queue 类:用于在多个进程之间传递数据,实现进程间的通信。
  • Pipe 函数:用于在两个进程之间创建一个管道,实现进程间的通信。
  • Lock 类:提供了一个简单的锁对象,用于在多个进程之间同步访问共享资源。

Process 类

        multiprocessing 模块提供了用于创建并管理进程的类和函数。其中,Process 类是用于创建进程的,并且可以在实例化 Process 类时创建进程并把将要执行的任务分配给这个进程(此时任务尚未执行),Process 类的结构如下所示

Process([group [, target [, name [, args [, kwargs]]]]])

参数介绍

  • group:进程组,若未使用则始终为 None
  • target:要在子进程中运行的函数,即子进程要执行的任务
  • name:子进程的名称
  • args:传递给 target 指向函数的位置参数,以元组的形式传入,例如,args=(1,2,'jove')
  • kwargs:传递给 target 指向函数的关键参数,以字典的形式传入,例如,kwargs=('name':'jove','age':18)

注意:

1、实例化时需要使用关键字的方式来指定参数

2、args 是一个元组,传入的参数必须有逗号,即使只传一个参数(例如,('子进程1',))

3、args 传入的参数是 target 指定函数的位置参数

方法介绍

  • p.start():启动子进程,该方法会调用子进程的 p.run() 方法来执行 target 指向的函数或可调用对象
  • p.run():该方法定义了子进程的行为,target 指向函数的调用正是由该方法来进行的,如果我们使用自定义类的方式实现多进程,则一定要在自定义类中实现该方法
  • p.terminate():强制终止子进程 p,该方法不会进行任何清理操作,如果 p 创建了子进程,该子进程就变成了僵尸进程,使用该方法需要注意这种情况。如果 p 还保存了一个锁那么也不会被释放,从而导致死锁现象的发生
  • p.is_alive():返回一个布尔值,表示子进程 p 是否仍在运行,如果 p 仍然运行将返回 True
  • p.join([timeout]):阻塞主进程(只能阻塞使用 start() 方法开启的进程),等待子进程 p 的终止或超时,即主线程处于等待状态,而子进程 p 是处于运行状态。timeout 参数(可选)指定最长等待时间
  • p.close():关闭底层资源,释放进程资源

属性介绍

  • p.daemon:守护进程标志,可以通过赋值操作把进程标志为守护进程,需要在 p.start() 之前进行设置。如果设置为 True,则子进程 p 会随着父进程的结束而结束,并且 子进程 p 无法创建自己的新进程;如果设置为 False,则子进程会继续运行,直到完成任务或被手动终止
  • p.name:返回进程的名称,也可以通过赋值操作来修改进程名称
  • p.pid:返回子进程的进程ID(Process ID)
  • p.exitcode:返回进程的退出码。如果进程尚未结束,则返回 None,如果为 -N 则表示被信号 N 结束了进程。
  • p.authkey:返回进程的身份验证密钥,用于验证进程之间的通信,默认是由 os.urandom() 随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功
  • p.sentinel:确认子进程是否为 Sentinel 进程(Sentinel 进程是一个特殊的子进程,在主进程退出时会自动终止所有子进程),是则返回 True

开启进程的两种方式

方法一:实例化 Process 类来开启进程

from multiprocessing import Process
import timedef task(name):print('%s is running' % name)time.sleep(3)print('%s is done' % name)if __name__ == '__main__':p1 = Process(target=task,args=('子进程1',)) # 等价于 Process(target=task,kwargs={'name': '子进程1'})p2 = Process(target=task, args=('子进程2',))p3 = Process(target=task, args=('子进程3',))p1.start()  # 仅仅只是给操作系统发送一个信号,从下面的输出可以看出“主进程”的打印是快于p1,p2,p3子进程的p2.start()p3.start()print('主进程')

 

方法二:自定义类继承 Process 类来开启进程

from multiprocessing import Process
import timeclass MyProcess(Process):def __init__(self,name):super().__init__()self.name=namedef run(self):print('%s is running' % self.name)time.sleep(3)print('%s is done' % self.name)if __name__ == '__main__':p1 = MyProcess('子进程1')p2 = MyProcess('子进程2')p3 = MyProcess('子进程3')p1.start()  # 仅仅只是给操作系统发送一个信号,从下面的输出可以看出“主进程”的打印是快于p1,p2,p3子进程的p2.start()p3.start()print('主进程')

注意:

1、在 Windows 中 Process() 的调用必须放到: 下,原因如下

由于 Windows 没有 fork,多处理模块启动一个新的 Python 进程并导入调用模块。 
如果在导入时调用 Process(),那么这将启动无限继承的新进程(或直到机器耗尽资源)。 
这是隐藏对 Process() 内部调用的原因,当使用  if __name__ == "__main__" 时,这个 if 语句中的语句将不会在导入时被调用。

2、进程之间的内存空间是相互隔离的

from multiprocessing import Process
import timen=100    # 全局变量,Windows中要把该变量放到 if __name__ == '__main__': 的前面
def work():global nn=0print('子进程内: ',n)if __name__ == '__main__':p=Process(target=work)p.start()time.sleep(2)    # 如果正常运行主进程会先打印,从输出看不出内存空间相互隔离print('主进程内: ',n)

二、join 方法

        在主进程运行过程中如果想并发地执行其他的任务,我们可以开启子进程,此时主进程的任务与子进程的任务分两种情况:

  • 情况一:在主进程的任务与子进程的任务彼此独立的情况下,主进程的任务先执行完毕后,主进程还需要等待子进程执行完毕,然后统一回收资源
  • 情况二:如果主进程的任务在执行到某一个阶段时,需要等待子进程执行完毕后才能继续执行,就需要有一种机制能够让主进程检测子进程是否运行完毕,在子进程执行完毕后才继续执行,否则一直在原地阻塞,这就是 join() 的作用
from multiprocessing import Process
import time
import random
import osdef task(name):print('%s is running' % name,os.getpid())time.sleep(random.randrange(1,3))print('%s is done' % name,os.getpid())if __name__ == '__main__':p = Process(target=task,args=('子进程1',))p.start()p.join(0.0001)    # 等待 p 停止,只等了0.0001秒就继续干自己的活了print('主进程')

        那使用了 join() 之后程序变成了串行吗?

        并不是的,我们要搞清楚 join() 是让那个进程做了等待,join() 是让主进程等待,并不是让子进程 p 进行等待,一下面的代码为例

from multiprocessing import Process
import time
import random
import osdef task(name):print('%s is running' % name,os.getpid())time.sleep(random.randrange(1,3))print('%s is done' % name,os.getpid())if __name__ == '__main__':p1 = Process(target=task,args=('子进程1',))p2 = Process(target=task, args=('子进程2',))p3 = Process(target=task, args=('子进程3',))p4 = Process(target=task, args=('子进程4',))p_list = [p1,p2,p3,p4]for p in p_list:p.start()for p in p_list:p.join()print('主进程')

         我们来分析一下这个过程,我们执行了 p1-p4.start() 之后系统中就已经有了四个并发的进程了,在开始运行之后又对 p1.join() ,这时 p1.join() 只会卡住主进程,而 p1 不结束主线程就会一直卡在原地,但其余的 p2-p4 仍然会继续运行。在主进程等待 p1 运行结束时,可能 p2-p4 早已经结束了,这样 p2-p4.join 直接通过检测,无需等待,所以4个 join() 花费的总时间仍然是耗费时间最长的那个进程运行的时间。

三、其他方法或属性

1、terminate() 和 is_alive()

#进程对象的其他方法一:terminate,is_alive
from multiprocessing import Process
import time
import random
import osclass MyProcess(Process):def __init__(self,name):self.name=namesuper().__init__()def run(self):print('%s is running' % self.name, os.getpid())time.sleep(random.randrange(1, 3))print('%s is done' % self.name, os.getpid())p1=MyProcess('子进程1')
p1.start()p1.terminate()  # 关闭进程,不会立即关闭,所以is_alive立刻查看的结果可能还是存活
print(p1.is_alive())    # 结果为Truetime.sleep(1)print('开始')
print(p1.is_alive())    # 结果为False

2、pid

  • pid:指的是当前进程的 id
  • ppid:指当前进程的父进程的 pid
from multiprocessing import Process
import time
import random
import osclass MyProcess(Process):def __init__(self,name):# self.name=name# super().__init__()# Process的__init__方法会执行self.name=MyProcess-1,# 所以加到这里,会覆盖我们的self.name=namesuper().__init__()self.name = namedef run(self):print('%s is running,pid is %s,ppid is %s' % (self.name, os.getpid(), os.getppid()))time.sleep(random.randrange(1, 3))print('%s is done,pid is %s,ppid is %s' % (self.name, os.getpid(), os.getppid()))if __name__ == '__main__':p1=MyProcess('子进程1')p1.start()print("%s is p1 process pid." % p1.pid)

僵尸进程与孤儿进程

一、僵尸进程

        僵尸进程(Zombie Process)是一种在 UNIX 操作系统中出现的现象。指的是一个已经完成运行任务的子进程,但其父进程没有调用 wait() 或者 waitpid() 获取子进程的状态信息并对该子进程进行善后处理(回收资源),那么这种进程的描述符仍然保存在系统当中继续占用系统资源。

        我们对僵尸进程的形成进一步分析。在 UNIX/Linux 系统当中,子进程通常都是通过父进程的建立,然后子进程再创建新的进程。而且子进程和父进程的运行和结束是一个异步的过程,即父进程无法预测子进程什么时候会结束,那就是说无法在子进程结束运行后就立马回收所有资源,而是要留下一部分的描述符给父进程获取子进程的状态信息。这就是 UNⅨ 提供的一种机制可以保证父进程可以在任意时刻获取子进程结束时的状态信息:

  1. 在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息,包括进程号(the process ID) 、退出状态(the termination status of the process)、运行时间(the amount of CPU time taken by the process)等
  2. 直到父进程通过 wait() 或 waitpid() 来获取该信息才释放。但这就造成了另一个问题了,如果父进程一直不调用 wait() 或 waitpid() 的话,那就要一直保留那段信息就不释放,其进程号就会一直被占用,而系统所能使用的进程号是有限的,如果大量的僵尸进程存在,将会因为没有可用的进程号而导致系统不能产生新的进程,可以说危害相当之大

        僵尸进程是每个子进程(init除外)结束时都会经历的,我们可以使用 ps 命令查看子进程的状态,如果是僵尸进程状态会是“Z”,但有的时候使用 ps 命令并不能查看得到,这是因为 ps 命令来不及看到就已经被父进程处理了。在这过程当中,如果父进程提前于子进程结束,那子进程将会由 init 接管,当子进程进入僵尸进程状态后将会以父进程的身份进行处理。

1、测试

#coding:utf-8
from multiprocessing import Process
import time,osdef run():print('子进程:',os.getpid())if __name__ == '__main__':p=Process(target=run)p.start()print('主进程:',os.getpid())time.sleep(1000)

2、解决方法

        正常的流程是等待父进程正常结束后调用 wait() 或 waitpid() 去回收僵尸进程,但如果父进程是一个死循环,那么该僵尸进程就会一直存在,对系统造成危害。

解决方法一:使用 kill -9 pid 命令杀死父进程

解决方法二:对开启的子进程使用 join()

# 引用 Python3 中的源码注释
class BaseProcess(object):......def join(self, timeout=None):'''Wait until child process terminates'''self._check_closed()assert self._parent_pid == os.getpid(), 'can only join a child process'assert self._popen is not None, 'can only join a started process'res = self._popen.wait(timeout)if res is not None:_children.discard(self)......# join() 中调用了 wait(),告诉系统释放僵尸进程
# 而 discard() 是把僵尸进程从自己的 _children 中剔除掉

解决方法三:使用 signal 模块

import signal...
...
# 在父进程中加入该语句来忽略 SIGCHLD 信号
signal(signal.SIGCHLD, signal.SIG_IGN)
...
..."""
SIGCHLD 信号:该信号是子进程改变状态的时候(比如子进程终止或停止)向父进程发送的,当我们使用 SIG_IGN 忽略该信号后,子进程的终止将会由 init 进程来接管。具体请参考这篇博客:http://blog.csdn.net/u010571844/article/details/50419798
"""

二、孤儿进程

        孤儿进程(Orphan Process)是指一个子进程在其父进程结束或者意外终止后,仍然在系统中运行的进程。此时内核就把孤儿进程托管给 init 进程,并由 init 进程对它们完成状态收集工作之类的善后工作,而 init 进程会循环地 wait() 它的已经退出的子进程,这样孤儿进程自然而然就会结束其的生命周期。因此孤儿进程并不会有什么危害。

1、测试

# 需要在 UNIX/Linux 中运行测试
import os
import sys
import timepid = os.getpid()
ppid = os.getppid()
print 'im father', 'pid', pid, 'ppid', ppid
pid = os.fork()# 执行pid=os.fork()则会生成一个子进程
# 返回值pid有两种值:
#    如果返回的pid值为0,表示在子进程当中
#    如果返回的pid值>0,表示在父进程当中if pid > 0:print 'father died..'sys.exit(0)# 保证主线程退出完毕
time.sleep(1)
print 'im child', os.getpid(), os.getppid()

2、解决方法

        系统的 init 进程会接管,无需处理。

对于僵尸进程和孤儿进程想了解更多的可以看看这篇博客: https://www.cnblogs.com/Anker/p/3271773.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/27974.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux : 环境变量

目录 一 环境变量 1.基本概念 二 常见环境变量 三 查看环境变量的方法 1.env:查看系统中所有环境变量 2. echo $NAME 四 如何不带路径也能运行的自己的程序 1.将自己的程序直接添加到PATH指定的路径下 五 环境变量与本地变量 1.本地变量 2. 环境变量 六C、C中main()…

【YashanDB认证】yashandb23.3.1 个人版单机部署安装实践

YCA报名链接如下: YashanDB|崖山数据库系统YashanDB学习中心-YCA认证详情 目前免费 主要参考文档: 单机(主备)部署 | YashanDB Doc 另外还参考摩天轮文章: YashanDB 23.2.9.101 企业版安装步骤抢先看! - 墨天轮 …

Stiring-PDF:开源免费的PDF文件处理软件

Stiring-PDF是一款开源免费且比较好用的PDF文件处理工具。 Stiring-PDF官网网址为:https://www.stiringpdf.com/。Stiring-PDF是一款专业的PDF文件处理工具,支持Windows和macOS操作系统;提供丰富的PDF编辑和转换功能,适用于日常工…

docker-compose安装anythingLLM

1、anythingLLM的docker-compose文件 version: 3.8 services:anythingllm:image: mintplexlabs/anythingllm:latestcontainer_name: anythingllmports:- "23001:3001"cap_add:- SYS_ADMINenvironment:# Adjust for your environment- STORAGE_DIR/app/server/storage…

maven推送jar包到nexus

1.背景2.推送jar包到nexus3.从nexus拉取jar包4.release和snapshot区别 1.背景 本地虚拟机centos7.9(110.110.110.100)安装好了nexus 下面演示把本地的maven项目打包推送到nexus。 2.推送jar包到nexus 我项目的命名如下: 下面演示把这个项目jar包推送到nexus仓库 <groupI…

微信小程序上如何使用图形验证码

1、php服务器生成图片验证码的代码片段如下&#xff1a; 注意红框部分的代码&#xff0c;生成的是ArrayBuffer类型的二进制图片 2、显示验证码 显示验证码&#xff0c;不要直接image组件加上src显示&#xff0c;那样拿不到cookie&#xff0c;没有办法做图形验证码的验证&…

comfyui使用ComfyUI-AnimateDiff-Evolved, ComfyUI-Advanced-ControlNet节点报错解决

comfyui使用animate-diff生成动画&#xff0c;各种报错解决 报错1&#xff1a; ‘cond_obj’ object has no attribute ‘hooks’ 报错2&#xff1a; AdvancedControlBase.get_control_inject() takes 5 positional arguments but 6 were given 报错3&#xff1a; ‘ControlN…

centos搭建 Node.js 开发环境

Node.js &#xff0c;通常简称为Node&#xff0c;是一个事件驱动 I/O 服务端 JavaScript 环境&#xff0c;基于 Chrome V8引擎&#xff0c;具备速度快、性能强等特点&#xff0c;可用于搭建各类网络应用&#xff0c;及作为小程序后端服务环境。npm 和 npx 都是和 Node.js 相关的…

涨薪技术|JMeter异步接口测试实战

前言 异步接口是指在请求发送后&#xff0c;客户端并不会立即收到响应结果。与同步接口不同&#xff0c;异步接口需要等待一段时间后才能得到相应的结果。 通常情况下&#xff0c;异步接口可以通过消息队列或事件监听器来实现。当用户请求进入系统时&#xff0c;可以将任务提…

SAP MDG —— MDG on S/4HANA 2023 FPS03 创新汇总

文章目录 MDG 基于SAP S/4HANA 2023 FPS03的创新BP/C/S&#xff1a;消息控制BP/C/S&#xff1a;手工分配数据控制者MDG-F&#xff1a;使用S/4扩展数据校验功能生成式AI可用于协助自定义对象的数据变更/同时可总结批量变更的内容 MDG 基于SAP S/4HANA 2023 FPS03的创新 由于从S…

数据库基础(MySQL)

1. 数据库基础 1.1 什么是数据库 存储数据用文件就可以了&#xff0c;为什么还要弄个数据库 文件保存数据有以下几个缺点&#xff1a; 文件的安全性问题文件不利于数据查询和管理文件不利于存储海量数据文件在程序中控制不方便 数据库存储介质&#xff1a; 磁盘内存 为了…

第五天 Labview数据记录(5.2 Text文件读写)

5.2 Text文件读写 文本文件读写在程序中具有重要的作用&#xff0c;主要体现在以下几个方面&#xff1a; 1. 数据存储与持久化&#xff1b;2. 数据交换与共享&#xff1b;3. 日志记录&#xff1b;4. 配置管理&#xff1b;5. 数据备份与恢复&#xff1b;6. 用户输入与输出&…

校园快递助手小程序毕业系统设计

系统功能介绍 管理员端 1&#xff09;登录&#xff1a;输入账号密码进行登录 2&#xff09;用户管理&#xff1a;查看编辑添加删除 学生信息 3&#xff09;寄件包裹管理&#xff1a;查看所有的包裹信息&#xff0c;及物流信息 4&#xff09;待取件信息&#xff1a;查看已到达的…

Docker入门指南:Windows下docker配置镜像源加速下载

Windows下docker配置镜像源加速下载 docker的官方镜像是海外仓库&#xff0c;默认下载耗时较长&#xff0c;而且经常出现断站的现象&#xff0c;因此需要配置国内镜像源。 国内镜像源概述 国内现有如下镜像源可以使用 "http://hub-mirror.c.163.com", "http…

DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)示例2: 分页和排序

前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)示例2: 分页和排序📚前言📚页面效果📚指令…

数据结构:二叉树的链式结构及相关算法详解

目录 一.链式结构的实现 1.二叉树结点基本结构&#xff0c;初始化与销毁&#xff1a; 二.链式结构二叉树的几种遍历算法 1.几种算法的简单区分&#xff1a; 2.前序遍历&#xff1a; 3.中序遍历&#xff1a; 4.后序遍历&#xff1a; 5.层序遍历&#xff08;广度优先遍历B…

动态规划/贪心算法

一、动态规划 动态规划 是一种用于解决优化问题的算法设计技术&#xff0c;尤其适用于具有重叠子问题和最优子结构性质的问题。它通过将复杂问题分解为更简单的子问题&#xff0c;并保存这些子问题的解以避免重复计算&#xff0c;从而提高效率。 动态规划的核心思想 最优子结…

【实战篇】【深度解析DeepSeek:从机器学习到深度学习的全场景落地指南】

一、机器学习模型:DeepSeek的降维打击 1.1 监督学习与无监督学习的"左右互搏" 监督学习就像学霸刷题——给标注数据(参考答案)训练模型。DeepSeek在信贷风控场景中,用逻辑回归模型分析百万级用户数据,通过特征工程挖掘出"凌晨3点频繁申请贷款"这类魔…

软考中级-数据库-3.2 数据结构-数组和矩阵

数组 一维数组是长度固定的线性表&#xff0c;数组中的每个数据元素类型相同。n维数组是定长线性表在维数上的扩张&#xff0c;即线性表中的元素又是一个线性表。 例如一维数组a[5][a1,a2,a3,a4,a5] 二维数组a[2][3]是一个2行2列的数组 第一行[a11,a12,a13] 第二行[a21,a22,a23…

android亮灭屏流程分析

前言 亮灭涉及的东西非常多&#xff0c;因此单独写一个文档&#xff0c;进行详细说明&#xff0c;亮灭屏包括的东西不只是亮灭屏&#xff0c;还包括亮度调节、屏幕状态变化等东西。本文仅作学习使用&#xff0c;不涉及商业&#xff0c;侵权请联系删除。 framework层的学习链接…