Python之路(第三十七篇)并发编程:进程、multiprocess模块、建立进程方式、join()、守护进程

1、在python程序中的进程操做

 以前已经了解了不少进程相关的理论知识,了解进程是什么应该再也不困难了,运行中的程序就是一个进程。全部的进程都是经过它的父进程来建立的。所以,运行起来的python程序也是一个进程,那么也能够在程序中再建立进程。多个进程能够实现并发效果,也就是说,当程序中存在多个进程的时候,在某些时候,就会让程序的执行速度变快。在python中实现多进程须要借助python中强大的模块。html

 

2、multiprocess模块

python中的多线程没法利用多核优点,若是想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分状况须要使用多进程。Python提供了multiprocessing。 multiprocessing模块用来开启子进程,并在子进程中执行咱们定制的任务(好比函数),该模块与多线程模块threading的编程接口相似。python

  multiprocessing模块的功能众多:支持子进程、通讯和共享数据、执行不一样形式的同步,提供了Process、Queue、Pipe、Lock等组件。linux

​ 须要再次强调的一点是:与线程不一样,进程没有任何共享状态,进程修改的数据,改动仅限于该进程内。编程

Process类的介绍

建立进程的类windows

  Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化获得的对象,表示一个子进程中的任务(还没有启动)
  ​
  强调:
  1. 须要使用关键字的方式来指定参数
  2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号

  

参数介绍:安全

  
  group参数未使用,值始终为None
  ​
  target表示调用对象,即子进程要执行的任务
  ​
  args表示调用对象的位置参数元组,args=(1,2,'egon',)
  ​
  kwargs表示调用对象的字典,kwargs={'name':'egon','age':18}
  ​
  name为子进程的名称

  

方法介绍:网络

  
  p.start():启动进程,并调用该子进程中的p.run() 
  p.run():进程启动时运行的方法,正是它去调用target指定的函数,咱们自定义类的类中必定要实现该方法  
  ​
  p.terminate():强制终止进程p,不会进行任何清理操做,若是p建立了子进程,该子进程就成了僵尸进程,使用该方法须要特别当心这种状况。若是p还保存了一个锁那么也将不会被释放,进而致使死锁
  p.is_alive():若是p仍然运行,返回True
  ​
  p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,须要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程 
 

  

属性介绍:数据结构

  
  p.daemon:默认值为False,若是设为True,表明p为后台运行的守护进程,当p的父进程终止时,p也随之终止,而且设定为True后,p不能建立本身的新进程,必须在p.start()以前设置
  ​
  p.name:进程的名称
  ​
  p.pid:进程的pid
  ​
  p.exitcode:进程在运行时为None、若是为–N,表示被信号N结束(了解便可)
  ​
  p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络链接的底层进程间通讯提供安全性,这类链接只有在具备相同的身份验证键时才能成功(了解便可)

  

Process类的使用

注意:在windows中Process()必须放到# if name == 'main':下多线程

 

建立并开启子进程的两种方式并发

方式一

  
  #方式一:直接用函数
  ​
  import multiprocessing
  # from multiprocessing import Process  这种导入模块的方式能够在下面代码中直接写Process(target= ,args=)
  import time
  ​
  def hi(name):
      print("hello %s"%name)
      time.sleep(1)
  ​
  if __name__ == "__main__":
      p = multiprocessing.Process(target=hi,args=("nick",))
      p.start()
      p.join()
      print("ending...")

  


方式二

  
  #开启进程的方式二,在类中启动进程
  import time
  import multiprocessing
  ​
  class Foo(multiprocessing.Process):  #这里继承 multiprocessing.Process类
  ​
      def __init__(self,name):
          super().__init__()
          self.name = name
  ​
      def run(self):
          print("hello %s" % self.name)
          time.sleep(3)
  ​
  if __name__ == "__main__":
      p = Foo("nick")
      p.start()  #这里执行start()会直接调用类的run()方法
      # p.join()
      print("ending...")

  

 

进程直接的内存空间是隔离的

  
  import multiprocessing
  ​
  n = 100
  ​
  ​
  def work():
      global n
      n = 0
      print("子进程内的n", n)
  ​
  ​
  if __name__ == '__main__':
      p = multiprocessing.Process(target=work)
      p.start()
      print("主进程内的n", n)

  

输出结果

  
  主进程内的n 100
  子进程内的n 0

  

分析:因为子进程和主进程是隔离的,因此即便在子进程里有global关键字,主进程同一变量的值也没变。

Process对象的join方法

在主进程运行过程当中若是想并发地执行其余的任务,咱们能够开启子进程,此时主进程的任务与子进程的任务分两种状况

状况一:在主进程的任务与子进程的任务彼此独立的状况下,主进程的任务先执行完毕后,主进程还须要等待子进程执行完毕,而后统一回收资源。

状况二:若是主进程的任务在执行到某一个阶段时,须要等待子进程执行完毕后才能继续执行,就须要有一种机制可以让主进程检测子进程是否运行完毕,在子进程执行完毕后才继续执行,不然一直在原地阻塞,这就是join方法的做用。

例子

  
  from multiprocessing import Process
  import time
  ​
  def func(args):
      print('-----',args)
      time.sleep(2)
      print("end---")
  ​
  if __name__ == '__main__':
      p = Process(target=func,args=(1,))
      p.start()
      print('哈哈哈哈')
      p.join()#join()的做用是阻塞主进程,使得主进程等待子进程执行完才把本身结束
      print("主进程运行完了")

  

例子2

  
  from multiprocessing import Process
  import time
  import random
  def piao(name):
      print('%s is talking' %name)
      time.sleep(random.randint(1,3))
      print('%s is talking end' %name)
  ​
  p1=Process(target=piao,args=('nick',))
  p2=Process(target=piao,args=('jack',))
  p3=Process(target=piao,args=('pony',))
  p4=Process(target=piao,args=('charles',))
  ​
  p1.start()
  p2.start()
  p3.start()
  p4.start()
  ​
  #疑问:既然join是等待进程结束,那么像下面这样写,进程不就又变成串行的了吗?
  #固然不是了,必须明确:p.join()是让谁等?
  #很明显p.join()是让主线程等待p的结束,卡住的是主线程而绝非进程p,
  ​
  #详细解析以下:
  #进程只要start就会在开始运行了,因此p1-p4.start()时,系统中已经有四个并发的进程了
  #而p1.join()是在等p1结束,没错p1只要不结束主线程就会一直卡在原地,这也是问题的关键
  #join是让主线程等,而p1-p4仍然是并发执行的,p1.join的时候,其他p2,p3,p4仍然在运行,等#p1.join结束,可能p2,p3,p4早已经结束了,这样p2.join,p3.join.p4.join直接经过检测,无需等待
  # 因此4个join花费的总时间仍然是耗费时间最长的那个进程运行的时间
  p1.join()
  p2.join()
  p3.join()
  p4.join()
  ​
  print('主线程')
  ​
  ​
  #上述启动进程与join进程能够简写为
  # p_l=[p1,p2,p3,p4]
  #
  # for p in p_l:
  #     p.start()
  #
  # for p in p_l:
  #     p.join()
 

  

Process对象的其余方法或属性

进程对象的其余方法:terminate与is_alivename与pid

例子

  
  import multiprocessing
  import time
  ​
  ​
  def task(name, t):
      print("进程正在运行。。。%s--%s" % (name, time.asctime()))
      time.sleep(t)
      print("进程结束了%s--%s" % (name, time.asctime()))
  ​
  ​
  if __name__ == "__main__":
      p1 = multiprocessing.Process(target=task, args=("nick", 5), name="进程1")  # 注意这里的参数name不是函数的参数
      p2 = multiprocessing.Process(target=task, args=("nicholas", 2), name="进程2")
      p_list = []
      p_list.append(p1)
      p_list.append(p2)
      print("进程是否存活", p1.is_alive())
      for p in p_list:
          p.start()
          print(p.name)  # 进程.name输出进程的名称
          print(p.name, p.pid)  # 进程对象.pid也能够输出子进程的ID号,
      print("进程是否存活", p1.is_alive())  # 判断子进程是否存活
      # p2.terminate()#无论进程是否执行完,马上结束进程
      for p in p_list:
          p.join()
      print("ending...")

  

经过os模块查看pid和ppid

import multiprocessing
import os

class Foo(multiprocessing.Process):

    def __init__(self,name):
        super().__init__()
        self.name = name

    def run(self):
        print("hi %s,子进程号是%s"%(self.name,os.getpid()))#输出当前的进程的pid


if __name__ == "__main__":
    p = Foo("nick")
    p.start()
    print("主进程是%s"%os.getppid())  #这里的主进程好就是执行这个py文件的程序,这里是pycharm,
    # 若是用命令终端执行py文件则主进程是命令终端的号
    #os.getppid()是输出当前进程的父进程pid号
    #os.getpid()是输出当前的进程的pid

  

 

僵尸进程与孤儿进程(了解)

 一:僵尸进程(有害)
    僵尸进程:一个进程使用fork建立子进程,若是子进程退出,而父进程并无调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然

保存在系统中。这种进程称之为僵死进程。详解以下 ​ 咱们知道在unix/linux中,正常状况下子进程是经过父进程建立的,子进程在建立新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无

法预测子进程到底何时结束,若是子进程一结束就马上回收其所有资源,那么在父进程内将没法获取子进程的状态信息。 ​ 所以,UNⅨ提供了一种机制能够保证父进程能够在任意时刻获取子进程结束时的状态信息:
一、在每一个进程退出的时候,内核释放该进程全部的资源,包括打开的文件,占用的内存等。可是仍然为其保留必定的信息(包括进程号the process ID,

退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)
二、直到父进程经过wait / waitpid来取时才释放. 但这样就致使了问题,若是进程不调用wait / waitpid的话,那么保留的那段信息就不会释放,其

进程号就会一直被占用,可是系统所能使用的进程号是有限的,若是大量的产生僵死进程,将由于没有可用的进程号而致使系统不能产生新的进程. 此即为僵尸

进程的危害,应当避免。 ​   任何一个子进程(init除外)在exit()以后,并不是立刻就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每一个子进程在

结束时都要通过的阶段。若是子进程在exit()以后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。若是父进程能及时 处理,可能用ps命

令就来不及看到子进程的僵尸状态,但这并不等于子进程不通过僵尸状态。 若是父进程在子进程结束以前退出,则子进程将由init接管。init将会以父进程的

身份对僵尸状态的子进程进行处理。 ​ 二:孤儿进程(无害) ​   孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进

程对它们完成状态收集工做。 ​   孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工做。每当出现一个

孤儿进程的时候,内核就把孤 儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的

时候,init进程就会出面处理它的一切善后工做。所以孤儿进程并不会有什么危害。     三:僵尸进程危害场景: ​   例若有个进程,它按期的产 生一个子进程,这个子进程须要作的事情不多,作完它该作的事情以后就退出了,所以这个子进程的生命周期很短,可是,父进程

只管生成新的子进程,至于子进程 退出以后的事情,则一律漠不关心,这样,系统运行上一段时间以后,系统中就会存在不少的僵死进程,假若用ps命令查看的话,

就会看到不少状态为Z的进程。 严格地来讲,僵死进程并非问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程。所以,当咱们寻求如何消灭系统中大量的

僵死进程时,答案就是把产生大 量僵死进程的那个元凶枪毙掉(也就是经过kill发送SIGTERM或者SIGKILL信号啦)。枪毙了元凶进程以后,它产生的僵死进程

就变成了孤儿进 程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经僵死的孤儿进程

就能瞑目而去了。

  

 

守护进程

会随着主进程的结束而结束。

主进程建立守护进程

  其一:守护进程会在主进程代码执行结束后就终止

  其二:守护进程内没法再开启子进程,不然抛出异常:AssertionError: daemonic processes are not allowed to have children

注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止,无论守护进程执行没执行完。

 

例子1

  
  from multiprocessing import Process
  ​
  import time
  def foo():
      print(123)
      time.sleep(1)
      print("end123")
  ​
  def bar():
      print(456)
      time.sleep(3)
      print("end456")
  ​
  if __name__ == '__main__':
      p1=Process(target=foo)
      p2=Process(target=bar)
  ​
      p1.daemon=True ##必定要在p1.start()前设置,设置p1为守护进程,
      # 禁止p1建立子进程,而且父进程代码执行结束,p1即终止运行
      p1.start()
      p2.start()
      print("main-------") #只要终端打印出这一行内容,那么守护进程p1也就跟着结束掉了

  

例子2

  
  # 主进程代码运行完毕,守护进程就会结束
  from multiprocessing import Process
  ​
  import time
  ​
  ​
  def foo():
      print(123)
      time.sleep(1)
      print("end123")
  ​
  ​
  def bar():
      print(456)
      time.sleep(3)
      print("end456")
  ​
  ​
  if __name__ == '__main__':
  ​
      p1 = Process(target=foo)
      p2 = Process(target=bar)
  ​
      p1.daemon = True
      p1.start()
      p2.start()
      print("main-------")  # 打印该行则主进程代码结束,则守护进程p1应该被终止,
      # 可能会有p1任务执行的打印信息123,由于主进程打印main----时,p1也执行了,可是随即被终止
      #这时p1可能会被执行,也可能不会被执行

  

 

参考资料

[1]http://www.cnblogs.com/Anker/p/3271773.html

相关文章
相关标签/搜索