day 31

多进程

进程是正在运行的程序,当程序被加系统加载到内存中并被执行时,就是进程的形式
在现在的计算机中可以实现一个程序可以产生多个进程。就好像我们可以用过pycharm中运行多个程序一样。

PID和PPID

PID

pid主要是用来区别多个进程,就像身份证是我们每一个人的最可靠的标识一样,pid就是系统分配给每一个进程最真实的身份标识。

在pycharm中可以导入os模块通过os模块来获取进程的pid编号

import os
print(os.getpid())

PPID

当一个进程一个进程在运行过程中开启了另一个进程,例如在a进程运行的时候,因为任务需要开启了b进程,这时候b 就是a进程的子进程,而a就可以称为b的父进程,我们也可以通过ppid,pid来同时打印进程

id 编号可以得出两个不同的id编号,这里ppid是是打印的进程a的id编号,pid的打印结果是进程b的id编号。

def task(name):
    print(" name %s 子进程run!" % name)
    # print("son is :%s" % os.getpid())
    # print("father is :%s" % os.getppid())
    time.sleep(3)
    print("子进程 over!")

在pycharm中运行py文件也是相同的情况,这时候的子父进程的关系为pycharm就是运行着的python.exe的父进程,python.exe就是子进程,换做在cmd中运行py文件时那么cmd就是python.exe的父进程。


开启子进程的两个方法

开启子进程的时机是当进程中出现了耗时的操作时,如果一条进程跑下去的话,会导致程序阻塞,没有办法及时及时运行,接下来代码没有办法走。这时候就可以开启子进程把耗时复杂的操作丢过去,在一旁慢慢跑。这样我们就可以运行接下来的代码,提高进程的运行效率。
方法一 :实例化Process类

def task(name):
    print(" name %s 子进程run!" % name)
    # print("son is :%s" % os.getpid())
    # print("father is :%s" % os.getppid())
    time.sleep(3)
    print("子进程 over!")


if __name__ == '__main__':
    print("father :%s" % os.getpid())
    p = Process(target=task,args=("rose",))
    p.start() # 开启子进程   本质是向操作系统发送请求 让它启动进程    通常不可能立即开启
    print("任务结束!")

使用if __name__ == '__main__'的原因是在于mac版本的pycharm 与windows下的版本有些不同,相同的是,子进程都是copy一份数据放进一个内存空间去运行,不同的地方是linuix会将父进程的数据全部复制过去,而windows下是只拷贝一部分数据同时会将py文件也导入进去,这样如果不用__main__来做一个是否为自只执行的判断的话

就会出现递归调用,死循环了,这样程序就崩掉了,linuix的话就会知道父进程代码运行位置,会接者运行下去,

所以为了保证代码能够在两个平台上都可以使用建议加上if __name__ == '__main__'

方法二: 实例化Process类并覆盖run方法

import os
from multiprocessing import  Process
class MyProcess(Process):
    def __init__(self,name):
        super().__init__()
        self.name = name
    # 继承Procee覆盖run方法将要执行任务发到run中
    def run(self):
        print(self.name)
        print("子进程 %s running!" % os.getpid())
        print("子进程 %s over!" % os.getpid())

if __name__ == '__main__':
    # 创建时 不用再指定target参数了
    p = MyProcess("rose")
    p.start()
    print("父进程over!")

进程间内存相互隔离

子进程执行起来以后就是把数据拷贝了一份,在子进程中
对数据进行修改不会对父进程造成影响,因为两份
数据是存在两个内存空间中的,内存之间是互相隔离的
rom multiprocessing import Process
import time
name = "青椒"
def task():
    global name
    name = "rose"
    print("改完了!")
    print("子进程的%s" % name)


if __name__ == '__main__':
    p = Process(target=task)
    p.start()
    time.sleep(2)
    print(name)
#程序打印结果

    改完了!
    子进程的rose
    青椒


join函数

当调用了start函数开始执行子进程后,就是由操作系统来操作子进程,与应用程序无关,join函数
就是可以让父进程等到子进程结束后再继续执行。
案例1:
from multiprocessing import Process
import time

x=1000

def task():
    time.sleep(3)
    global x
    x=0
    print('儿子死啦',x)
if __name__ == '__main__':
    p=Process(target=task)
    p.start()

    p.join() # 让父亲在原地等
    print(x)

这里因为join函数的关系让父类进程一直等待子进程的结束,而子进程已经死掉了,父进程也不会执行,这样就形成了僵尸进程。

案例2:

def task(i):
    print('%s买烟去'% i)
    time.sleep(i)
    print('卖完了')

if __name__ == '__main__':
    t1 = time.time()
    p = Process(target=task,args=(1,))
    p1 = Process(target=task,args=(2,))
    p2 = Process(target=task,args=(3,))
    p.start()
    p1.start()
    p2.start()
    p.join()
    p1.join()
    p2.join()
    t2 = time.time()
    print(t2 - t1)
    print('挂了')

可以用来计算出进程从开始到结束的的最大时间差。


Process对象常用属性

from multiprocessing import Process
def task(n):
    print('%s is runing' %n)
    time.sleep(n)

if __name__ == '__main__':
    start_time=time.time()
    p1=Process(target=task,args=(1,),name='任务1')
    p1.start() # 启动进程
    print(p1.pid) # 获取进程pid
    print(p1.name) # 获取进程名字
    p1.terminate() # 终止进程
    p1.join() # 提高优先级
    print(p1.is_alive()) # 获取进程的存活状态
    print('')

孤儿进程与僵尸进程

孤儿进程指的是开启子进程后,父进程先于子进程终止了,那这个子进程就称之为孤儿进程

例如:qq聊天中别人发给你一个链接,点击后打开了浏览器,那qq就是浏览器的父进程,然后退出qq,此时浏览器就成了孤儿进程

孤儿进程是无害的,有其存在的必要性,在父进程结束后,其子进程会被操作系统接管。

僵尸进程指的是,当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。如果父进程先退出 ,子进程被操作系统接管,子进程退出后操作系统会回收其占用的相关资源!

僵尸进程的会占用系统内存资源,过多会造成系统想要开启新的进程时,没有空间开辟。严重占用资源。

并发与并行,阻塞与非阻塞

 并发指的是多个进程同时发起了,但本质上是进程之间来回切换,速度很快,给用户的感觉上是在同时进行。

并行是指多个事件同时进行着。

阻塞与非阻塞指的是程序的状态

阻塞状态是因为程序遇到了IO操作,或是sleep,导致后续的代码不能被CPU执行

非阻塞与之相反,表示程序正在正常被CPU执行

补充:进程有三种状态 : 就绪状态,运行状态和阻塞状态

多道技术会在进程执行时间过长或遇到IO时自动切换其他进程,意味着IO操作与进程被剥夺CPU执行权都会造成进程无法继续执行。

进程相关理论

进程的创建

但凡是硬件,都需要有操作系统去管理,只要有操作系统,就有进程的概念,就需要有创建进程的方式,一些操作系统只为一个应用程序设计,比如微波炉中的控制器,一旦启动微波炉,进程就已经存在。

  而对于通用系统(跑很多应用程序),需要有系统运行过程中创建或撤销进程的能力,主要分为4中形式创建新的进程

  1. 系统初始化(查看进程linux中用ps命令,windows中用任务管理器,前台进程负责与用户交互,后台运行的进程与用户无关,运行在后台并且只在需要时才唤醒的进程,称为守护进程,如电子邮件、web页面、新闻、打印)

  2. 一个进程在运行过程中开启了子进程(如nginx开启多进程,os.fork,subprocess.Popen等)

  3. 用户的交互式请求,而创建一个新进程(如用户双击暴风影音)

  4. 一个批处理作业的初始化(只在大型机的批处理系统中应用)

  

  无论哪一种,新进程的创建都是由一个已经存在的进程执行了一个用于创建进程的系统调用而创建的:

  1. 在UNIX中该系统调用是:fork,fork会创建一个与父进程一模一样的副本,二者有相同的存储映像、同样的环境字符串和同样的打开文件(在shell解释器进程中,执行一个命令就会创建一个子进程)

  2. 在windows中该系统调用是:CreateProcess,CreateProcess既处理进程的创建,也负责把正确的程序装入新进程。

 

  关于创建的子进程,UNIX和windows

  1.相同的是:进程创建后,父进程和子进程有各自不同的地址空间(多道技术要求物理层面实现进程之间内存的隔离),任何一个进程的在其地址空间中的修改都不会影响到另外一个进程。

  2.不同的是:在UNIX中,子进程的初始地址空间是父进程的一个副本,提示:子进程和父进程是可以有只读的共享内存区的。但是对于windows系统来说,会重新加载程序代码。

 

进程的销毁

  1. 正常退出(自愿,如用户点击交互式页面的叉号,或程序执行完毕调用发起系统调用正常退出,在linux中用exit,在windows中用ExitProcess)

  2. 出错退出(自愿,python a.py中a.py不存在)

  3. 严重错误(非自愿,执行非法指令,如引用不存在的内存,1/0等,可以捕捉异常,try...except...)

  4. 被其他进程杀死(非自愿,如kill -9)

原文地址:https://www.cnblogs.com/1624413646hxy/p/10957579.html