Python的进程,以及进程同步,守护进程详细解读

文章目录

1、进程 process

进程的概念:(Process)
进程就是正在运行的程序,它是操作系统中,资源分配的最小单位.
资源分配:分配的是cpu和内存等物理资源
进程号是进程的唯一标识

同一个程序执行两次之后是两个进程
进程和进程之间的关系: 数据彼此隔离,通过socket通信

并行和并发
并发:一个cpu同一时间不停执行多个程序
并行:多个cpu同一时间不停执行多个程序

(1)cpu的进程调度方法

先来先服务fcfs(first come first server):先来的先执行
短作业优先算法:分配的cpu多,先把短的算完
时间片轮转算法:每一个任务就执行一个时间片的时间.然后就执行其他的.
多级反馈队列算法   前三个的综合体

越是时间长的,cpu分配的资源越短,优先级靠后
越是时间短的,cpu分配的资源越多

按先来先服务,分配相同时间没做完的往后排,时间分配更少

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

当进程由阻塞态变成就绪态,CPU会回来从上次阻塞的位置往下执行。记住状态使用的原理类似生成器的yield

同步 异步 / 阻塞 非阻塞
场景在多任务当中
同步:必须等我这件事干完了,你在干,只有一条主线,就是同步
异步:没等我这件事情干完,你就在干了,有两条主线,就是异步
阻塞:比如代码有了input,就是阻塞,必须要输入一个字符串,否则代码不往下执行
非阻塞:没有任何等待,正常代码往下执行.

# 同步阻塞 :效率低,cpu利用不充分
# 异步阻塞 :比如socketserver,可以同时连接多个,但是彼此都有recv
# 同步非阻塞:没有类似input的代码,从上到下执行.默认的正常情况代码
# 异步非阻塞:效率是最高的,cpu过度充分,过度发热

import os,time
"""
# ps -aux 查看进程号
# ps -aux | grep 2784 过滤查找2784这个进程

# 强制杀死进程
kill -9 进程号

# 获取当前进程号,每次执行重新生成进程号
res = os.getpid()
print(res)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

获取进程号另一方法:current_process().ident
在这里插入图片描述

#获取当前进程的父进程

res = os.getppid()
print(res)
"""

#引入进程模块

from multiprocessing import Process

Process进程类说明:
Process([group [,target[,name[,args[,kwargs]]]]])

group:指定进程组,目前只能使用None,默认即可
target:执行的目标任务名 一个函数或一个方法 传递的是方法名。如果方法名后面加了括号,相当于让主进程去执行该任务
name:进程名字 不用设置,使用默认
args:以元祖方式给进程传参
kwargs:以字典方式给进程传参,字典的key一定要和函数的参数名一致

Process创建的实例对象的常用方法:

start():启动子进程实例(创建子进程)
join():等待进程执行结束
terminate():不管任务是否完成,立即终止子进程

Process创建的实例对象的常用属性:
name:当前进程的别名,默认Process-N,N为从1开始递增的整数

(2)进程的使用

def func():
    # 1.子进程id:3561,2.父进程id:3560
    print("1.子进程id:{},2.父进程id:{}".format(os.getpid(),os.getppid()))

if name == main:
# 创建子进程 ,返回进程对象,指定要完成的任务,一般是指定一个函数
p = Process(target=func)
# 调用子进程
p.start()

# 3.主进程id:3560,4.父进程id:3327
print(“3.主进程id:{},4.父进程id:{}”.format(os.getpid(),os.getppid()))
“”"
当前文件的进程是 通过Process创建的进程的父进程
Process创建了子进程
Windows无法实现fork,所以在Windows里面创建子进程的代码,要在if name == main:里面执行。否则有递归的结果

在这里插入图片描述

这是典型的异步,上面的代码没走完,就走下面的代码。两条主线
其实是先走主进程代码,再走子进程代码
是因为创建子进程后,要为子进程准备资源,稍有阻塞,此时CPU就会去执行下面的进程,所以主进程执行别子进程快。
CPU永远不等待在阻塞态的任务,只会去执行就绪态的任务,把它变成运行态

进程对象有很多内置方法
在这里插入图片描述

Windows如果不把创建子进程的代码放到__main__里面,执行将报错。Windows无法实现fork,遇到Process,就相当于把当前文件放到另一个文件中执行,一直循环下去,导致内存被撑爆
加上if __name__ == '__main__':  只有在主进程里面,才会执行下面的代码,子进程中不执行,所以达到预期

 
 

在这里插入图片描述

(3)创建带有参数的进程

args:以元祖方式给进程传参,args里面的参数顺序要和函数里面的参数顺序一致
kwargs:以字典方式给进程传参,字典的key一定要和函数的参数名一致

def func(n):
    time.sleep(1)
    for i in range(1,n+1): # 0 ~ n-1
        print(i)
        print("1.子进程id:{},2.父进程id:{}".format(os.getpid(),os.getppid()))

if name == main:
n = 6
# target=指定任务 args = 参数元组
p = Process(target=func , args=(n,))
p.start()

for i in range(1,n+1):
print("" i)

获取进程名 multiprocessing.current_process()
在这里插入图片描述

子进程执行是无序的,具体顺序是由操作系统调度决定的
在这里插入图片描述

(4) 进程之间的数据彼此隔离

total = 100
def func():
    global total
    total +=1
    print(total)

if name == main:
p = Process(target=func)
p.start()

time.sleep(1)
print(total)

子进程把数据改了,但在当前进程中total的值还是100,所以进程间的数据是彼此隔离的
在这里插入图片描述

进程需要注意的事项:
1.子进程之间不共享全局变量
2.主进程会等待所有子进程执行结束再结束

(5)进程之间的异步性

1.多个进程之间是异步的并发程序,因为cpu调度策略问题,不一定先执行哪一个任务
默认来看,主进程执行速度稍快于子进程,因为子进程创建时,要分配空间资源可能会阻塞
阻塞态,cpu会立刻切换任务,以让程序整体的速度效率最大化

2.默认主进程要等待所有的子进程执行结束之后,再统一关闭程序,释放资源
若不等待,子进程可能不停的在系统的后台占用cpu和内存资源形成僵尸进程.
为了方便进程的管理,主进程默认等待子进程.再统一关闭程序;

def func(n):
    print("1.子进程id:{},2.父进程id:{}".format(os.getpid(),os.getppid()) , n )

if name == main:
for i in range(1,11):
p = Process(target=func,args=(i,))
p.start()

print("主进程执行结束了 … " , os.getpid() )

在这里插入图片描述

在这里插入图片描述

每次执行,执行顺序是无序的,具体顺序由CPU调度策略决定。谁快就执行谁
体现了进程之间的异步性

看到上面主进程代码中间就打印了,不代表主进程就结束了,实际上主进程只是代码执行结束了,资源空间还没释放。主进程还在
等待所有子进程执行结束之后,再统一关闭程序,释放资源

#根据进程编号杀死指定进程,第一个参数是指定的id,第二个参数是kill杀死进程的参数编号 -9强制杀死
os.kill(dance_process_id, 9)

#判断主模块再执行,程序入口模块,标准python写法都需要写

if __name__ == '__main__':
    test1_process = multiprocessing.Process(target=test1)
    # 1、让子进程成为守护主进程,主进程执行完退出后,子进程就销毁
    # test1_process.daemon = True
    test1_process.start()
    # 人为设置主进程0.6秒后退出。实际上要等子进程执行结束,主进程才结束
    time.sleep(0.6)
    #2、设置主进程结束之前,先让子进程销毁
    test1_process.terminate()
    print('over')
<span class="token comment"># 如果要人为设定主进程退出,子进程销毁</span>
<span class="token comment"># 解决办法,1、让子进程称为守护主进程,主进程退出,子进程销毁,子进程依赖主进程</span>
<span class="token comment"># 2、让主进程退出之前让子进程销毁</span>

2、同步主进程和子进程 : join

必须等待当前的这个子进程执行结束之后,再去执行下面的代码;,用来同步子父进程;
在主进程运行过程中如果想并发地执行其他的任务,我们可以开启子进程,此时主进程的任务与子进程的任务分两种情况

情况一:

在主进程的任务与子进程的任务彼此独立的情况下,主进程的任务先执行完毕后,主进程还需要等待子进程执行完毕,然后统一回收资源。 这种是没有join方法

情况二:

如果主进程的任务在执行到某一个阶段时,需要等待子进程执行完毕后才能继续执行,

就需要有一种机制能够让主进程检测子进程是否运行完毕,在子进程执行完毕后才继续执行,否则一直在原地阻塞,这就是join方法的作用

from multiprocessing import Process
import time

(1) join 的基本使用

在这里插入图片描述

def func():
    print("发送第一封邮件 :  我的亲亲领导,你在么?")    

if name == main:
p = Process(target=func)
p.start()
# time.sleep(0.1)
p.join()
print(“发送第二封邮件 : 我想说,工资一个月给我涨到6万”)

一般情况下,主进程的代码先执行了
在这里插入图片描述

如果先让主进程延时一会再执行,或者使用进程同步,则等上面的进程执行结束,才执行下面的代码
延时的办法比较low,而且浪费时间。一般我们采用进程同步的办法:等待当前进程执行结束,立马执行下面的代码。没有任何等待
语法:进程对象.join()
在这里插入图片描述

(2) 多进程场景中的join

def func(i):
    time.sleep(1)
    print("发送第一封邮件{} :  我的亲亲领导,你在么?".format(i))

if name == main:
lst = []
for i in range(1,11):
p = Process(target=func,args=(i,))
p.start()
# join 写在里面会导致程序变成同步,不能写在里面
lst.append(p)

# 把所有的进程对象都放在列表中,统一使用.join进行管理;
for i in lst:
i.join()

print(“发送第二封邮件 : 我想说,工资一个月给我涨到6万”)

如果在创建子进程的循环里面设置进程同步,如果每个进程有等待,则每个进程需要等上一个进程执行完毕才能继续执行下一个进程,比较费时
在这里插入图片描述

所以,我们把所有的进程对象都放在列表中,统一使用.join进行管理; 这样,只是第一次需要等待,所有子进程当做一个整体
在这里插入图片描述

3、使用自定义进程类,创建进程

(1) 基本语法

import os

class MyProcess(Process):
def run(self):
print(“1.子进程id:{},2.父进程id:{}”.format(os.getpid(),os.getppid()))

if name == main:
p = MyProcess()
p.start()

重写父类方法,方法名不能变。run方法写自己进程类的逻辑
在这里插入图片描述

原码:
在这里插入图片描述

(2) 带有参数的自定义进程类

class MyProcess(Process):

def init(self,name):
# 手动调用一下父类的构造方法,完成系统成员的初始化;
super().init()
self.name = name

def run(self):
print(“1.子进程id:{},2.父进程id:{}”.format(os.getpid(),os.getppid()))
print(self.name)

if name == main:
p = MyProcess(“我是参数”)
p.start()

自定义带有参数的进程类,在run方法里面必须加载下父类__init__方法,不然相当于只执行在本类中自定义的构造方法,不再调用父类的构造方法,就缺少了父类构造方法的很多初始化成员。程序运行会报错
在这里插入图片描述

加载父类构造方法,就不再报错。可以正常传参
在这里插入图片描述

总结:
创建子进程两种方式:
1.通过系统的multiprocess.Process类创建
2.自定义类继承Process,并重新父类run方法,带参数就重写__init__方法,并且手动通过super()调用父类__init__方法来创建。自己创建类自己可以封装很多成员

4、守护进程

守护进程守护的是主进程,当主进程所有代码执行完毕之后,立刻强制杀死守护进程;

多进程,多线程,多携程主要运用在爬虫领域
在这里插入图片描述

(1) 基本语法

from multiprocessing import Process
import time

def func():
# time.sleep(1)
print(“start… 当前的子进程”)
print(“end … 当前的子进程”)

if name == main:
p = Process(target=func)
# 在进程启动之前,设置守护进程
p.daemon = True
p.start()

print("主进程执行结束 … ")

运行
运行

此时守护进程p守护的是当前文件主进程,当主进程代码执行完毕之后,该守护进程会被立刻杀死。所以子进程内容没被打印出来
设置守护进程必须在start之前
在这里插入图片描述

daemon是父类的一个方法,通过装饰器可以方法变属性
在这里插入图片描述

(2) 多个子进程的守护场景

默认主进程等待所有非守护进程,也就是子进程执行结束之后,在关闭程序,释放资源
守护进程只要在主进程代码执行结束时,就会自动关闭;

def func1():
    print("start ... func1 执行当前子进程 ... ")
    print("end ...   func1 结束当前子进程 ... ")

def func2():
count = 1
while True:
print("" count)
time.sleep(1)
count += 1

if name == main:
p1 = Process(target=func1)
p2 = Process(target=func2)

# 把p2这个进程变成守护进程;
p2.daemon = True
p1.start()
p2.start()

print("主进程执行结束 … ")

p2设为守护进程,当主进程代码执行完毕之后,该守护进程会被立刻杀死,没执行其中代码。守护进程能否执行,取决于主进程代码执行快慢
主进程代码要是执行的慢,守护进程可能会执行一部分,若是主进程代码执行的快,守护进程可能来不及执行
在这里插入图片描述

让主进程代码晚点执行,守护进程可执行一部分
在这里插入图片描述

如果不设为守护进程,func2中的代码正常执行
在这里插入图片描述

(3) 守护进程用途: 监控报活

def alive():
    while True:
        print("3号服务器向总监控服务器发送报活信息: i am ok~")
        time.sleep(1)

def func():
while True:
try:
print(“3号服务器负责抗住3万用户量的并发访问…”)
time.sleep(3)

# 主动抛出执行错误的异常,触发except分支
raise RuntimeError

except:
print(“3号服务器扛不住了… 快来修理我…”)
break

if name == main:
p1 = Process(target=alive)
p2 = Process(target=func)

p1.daemon = True
p1.start()
p2.start()

# 必须等待p2这个子进程执行完毕之后,再放行主进程下面的代码
# 下面主进程代码执行结束,立刻杀死守护进程,失去了报活功能;
p2.join()

print("主进程执行结束 … ")

我们把监控服务设为守护进程,当报异常,监控程序也要停止发送消息

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值