多进程 与并发

时间:2022-08-28 20:27:12

1.进程与程序

  进程:是正在进行的程序;

  程序:是一堆代码,当这堆代码被系统加载到内存执行时,产生进程

  注:一个程序是可以产生多个进程,就像我们可以同时运行多个qq,会形成多个进程

2.PID 与PPID

  pid:进程编号,用于区分多ge进程个;

  ppid:当前进程的父进程的进程编号,

  可以用os模块来获取pid/ppid        eg:       os.getpid()              os.getppid()

  如果在pycharm中运行py文件,那pycharm就是这个python.exe的父进程,如果从cmd运行py文件,那cmd就是python.exe的父进程

3.进程与程序
 1.并发与并行 

  并发:指的是多个事件同时发生,本质是任务间的切换给人的感觉是同时在进行,称之为伪并行   例如洗衣服和做饭,同时发生了

  并行:指的是多个事件同时进行种,例如一个人在写代码另一个人在写书,这两件事件是同时在进行的,一个人是无法真正的并行执行任务

 2.阻塞与非阻塞,是程序的状态

  阻塞状态:是程序遇到io操作,比如sleep,print,input,导致后续的代码不能被CPU执行

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

 3.进程的三种状态间的转换

  就绪 到  执行  要 进程调度    ,   执行 到 阻塞  要有io  ,                          阻塞 到就绪 要完成io

                执行 到 就绪  cpu运行过长,时间片用完

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

 4.进程的创建

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

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

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

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

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

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

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

 

 5.进程的销毁

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

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

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

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

进程的层次结构

      无论UNIX还是windows是先相同之处都是 需要将数据copy一份给子进程 这样子进程才知道要干什么  

  linux 会将父进程的所有数据 完全copy , 
  windows 会copy 一部分数据 同时会导入py文件来执行 这样一来递归开进程
  linux 拿到父进程知道代码位置 继续执行
  建议都加上判断 可以保证两个平台都能用
  1. 在UNIX中所有的进程,都是以init进程为根,组成树形结构。父子进程共同组成一个进程组,这样,当从键盘发出一个信号时,该信号被送给当前与键盘相关的进程组中的所有成员。

  2. 在windows中,没有进程层次的概念,所有的进程都是地位相同的,唯一类似于进程层次的暗示,是在创建进程时,父进程得到一个特别的令牌(称为句柄),该句柄可以用来控制子进程,但是父进程有权把该句柄传给其他子进程,这样就没有层次了。

 

6.python中开启子进程的两种方式

方式1:

实例化Process类

from multiprocessing import Process
import time

def task(name):
   print('%s is running' %name)
   time.sleep(3)
   print('%s is done' %name)
if __name__ == '__main__':
   # 在windows系统之上,开启子进程的操作一定要放到这下面
   # Process(target=task,kwargs={'name':'egon'})
   p=Process(target=task,args=('jack',))
   p.start() # 向操作系统发送请求,操作系统会申请内存空间,然后把父进程的数据拷贝给子进程,作为子进程的初始状态
   print('======主')

方式2:

继承Process类 并覆盖run方法

from multiprocessing import Process
import time

class MyProcess(Process):
   def __init__(self,name):
       super(MyProcess,self).__init__()
       self.name=name

   def run(self):
       print('%s is running' %self.name)
       time.sleep(3)
       print('%s is done' %self.name)
if __name__ == '__main__':
   p=MyProcess('jack')
   p.start()
   print('主')

需要注意的是

1.在windows下 开启子进程必须放到__main__下面,因为windows在开启子进程时会重新加载所有的代码造成递归创建进程

2.第二种方式中,必须将要执行的代码放到run方法中,子进程只会执行run方法其他的一概不管

 

进程间内存相互隔离

 

7.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)

案例2:

from multiprocessing import Process
import time,random

x=1000

def task(n):
   print('%s is runing' %n)
   time.sleep(n)

if __name__ == '__main__':
   start_time=time.time()

   p1=Process(target=task,args=(1,))
   p2=Process(target=task,args=(2,))
   p3=Process(target=task,args=(3,))
   p1.start()
   p2.start()
   p3.start()

   p3.join() #3s
   p1.join()
   p2.join()

   print('主',(time.time() - start_time))

   start_time=time.time()
   p_l=[]
   for i in range(1,4):
       p=Process(target=task,args=(i,))
       p_l.append(p)
       p.start()
   for p in p_l:
       p.join()
   
   print('主',(time.time() - start_time))

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,此时浏览器就成了孤儿进程

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

 

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

僵尸进程的危害:,子进程先结束,保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生[僵死进程],将因为没有可用的进程号而导致系统不能产生新的进程。