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

简介: 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')

    # 如果要人为设定主进程退出,子进程销毁
    # 解决办法,1、让子进程称为守护主进程,主进程退出,子进程销毁,子进程依赖主进程
    # 2、让主进程退出之前让子进程销毁

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("主进程执行结束  .... ")


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


相关文章
|
4天前
|
Java 测试技术 Python
Python的多线程允许在同一进程中并发执行任务
【5月更文挑战第17天】Python的多线程允许在同一进程中并发执行任务。示例1展示了创建5个线程打印"Hello World",每个线程调用同一函数并使用`join()`等待所有线程完成。示例2使用`ThreadPoolExecutor`下载网页,创建线程池处理多个URL,打印出每个网页的大小。Python多线程还可用于线程间通信和同步,如使用Queue和Lock。
17 1
|
4天前
|
缓存 算法 Java
操作系统(8)---进程的同步与互斥以及信号量机制(万字总结~)(4)
操作系统(8)---进程的同步与互斥以及信号量机制(万字总结~)
19 0
|
4天前
操作系统(8)---进程的同步与互斥以及信号量机制(万字总结~)(3)
操作系统(8)---进程的同步与互斥以及信号量机制(万字总结~)
15 0
|
4天前
|
C++ 调度
操作系统(8)---进程的同步与互斥以及信号量机制(万字总结~)(2)
操作系统(8)---进程的同步与互斥以及信号量机制(万字总结~)
14 0
|
4天前
|
算法 安全 调度
操作系统(8)---进程的同步与互斥以及信号量机制(万字总结~)(1)
操作系统(8)---进程的同步与互斥以及信号量机制(万字总结~)
16 0
操作系统(8)---进程的同步与互斥以及信号量机制(万字总结~)(1)
|
4天前
|
数据处理 Python
Python并发编程:实现高效的多线程与多进程
Python作为一种高级编程语言,提供了强大的并发编程能力,通过多线程和多进程技术,可以实现程序的并发执行,提升系统的性能和响应速度。本文将介绍Python中多线程和多进程的基本概念,以及如何利用它们实现高效的并发编程,解决实际开发中的并发性问题。
|
6天前
|
存储 安全 Linux
【Linux】详解进程通信中信号量的本质&&同步和互斥的概念&&临界资源和临界区的概念
【Linux】详解进程通信中信号量的本质&&同步和互斥的概念&&临界资源和临界区的概念
|
6天前
|
监控 Python
python过滤指定进程
python过滤指定进程
16 1
|
6天前
|
运维 监控 Ubuntu
Python实现ubuntu系统进程内存监控
Python实现ubuntu系统进程内存监控
17 1
|
6天前
|
开发者 Python
在Python中查询进程信息的实用指南
在Python中查询进程信息的实用指南
11 2
http://www.vxiaotou.com