33.Python并发编程之多进程

Python并发编程之多进程

  1. 什么是进程?
    进程是一个抽象的概念,进程的概念起源于操作系统,正在进行的一个过程或者一个过程的总和,而负责执行过程的则是CPU。

    • 进程与程序的区别
      程序仅仅只是一堆文件、一堆代码而已,而进程指的是程序的运行过程或过程的总和。
    • 并发与并行
      无论是并行还是并发,在用户看来都是'同时'运行的,不管是进程还是线程,都只是一个任务而已,真是干活的是CPU,CPU来做这些任务,而一个CPU同一时刻只能执行一个任务。
      • 并发:是伪并行,即看起来是同时运行。单个CPU+多道技术就可以实现并发,(并行也属于并发)。
      • 并行:同时运行,只有具备多个CPU才能实现并行。
    • 多道技术
      多道的产生背景是想要在单核CPU的情况下实现多个进程并发执行,具有两大核心特点:
      • 空间上的复用(多道程序复用内存的空间):多道程序同时读入内存,等待被CPU执行,即产生了多个进程;进程之间的内存地址空间是相互隔离的,而且这种隔离是物理级别实现的。
      • 时间上的复用(多道程序复用CPU的时间):正在执行的进程遇到了I/O操作 或 正在执行的进程占用CPU时间过长 或 来了一个优先级更高的进程,操作系统都会强制收回当前进程的CPU使用权限,并将其分配给其他进程。
      • 因此,若要提升程序的执行效率,需要减少或降低I/O操作。

  1. 开启子进程的两种方式

    • 第一种

      from multiprocessing import Process
      import time
      
      def task(name):
         print(F'{name} is running')
         time.sleep(3)
         print(F'{name} is done')
      
      if __name__ == '__main__': # 在Windows系统之上,开启子进程的操作一定要放到这下面
         # if __name__ == '__main__'的意思是:当.py文件被直接运行时,if __name__ == '__main__'之下的代码块将被运行;
         # 当.py文件以模块形式被导入时,if __name__ == '__main__'之下的代码块不被运行。
         P = Process(target=task, args=('Python',)) # args= 后面是一个元组,因此一个参数的时候必须加逗号
         # Process(target=task,kwargs={'name':'Python'}) # kwargs= 后面是一个字典,这两种传参方式任选一种
         P.start() # 向操作系统发送申请内存空间请求,注意,是发请求给操作系统,至于操作系统怎么申请内存空间不管,然后把父进程的数据拷贝给子进程,作为子进程的初始状态
         print('父进程')
      
    • 第二种

      from multiprocessing import Process
      import time
      
      class MyProcess(Process):  # 自己定义一个类,并继承Process
         def __init__(self, name):
            super(Process, self).__init__()
            self.name = name
         def run(self): # 规定必须要定义run方法
            print(F'{self.name} is running')
            time.sleep(3)
            print(F'{self.name} is done')
         
      if __name__ == "__main__":
         p = MyProcess('Python')
         p.start()  # 规定,start()调用的是MyProcess()中的run(self)方法
         print('父进程')
      

  1. 父进程等待子进程结束

    • 验证进程的内存空间相互隔离

      from multiprocessing import Process
      import time
      x = 1
      
      # 子进程要执行的代码
      def task():
         time.sleep(3)
         global x
         x = 0
         print('子进程结束',x)
         # 打印结果:子进程结束 0
      
      # 开启子进程
      if __name__ == '__main__':
         p = Process(target=task)
         p.start()
      
         p.join() # 让父进程等待子进程结束,子进程结束后才会执行下一行代码,也有回收僵尸进程的效果
         print(x)
         # 打印结果:1
      
    • 验证父进程等待子进程结束后才会结束

      from multiprocessing import Process
      import time
      import random
      
      # 子进程要执行的代码
      def task(n):
         print(F'{n} is running')
         time.sleep(random.randint(1,10))
      # 开启子进程
      start_time = time.time()
      p_l= []
      if __name__ == '__main__':
         # 申请开启5次子进程
         for i in range(5):
            p = Process(target=task, args=(i,))
            p_l.append(p)
            p.start() # 由于p.start()只是向操作系统申请开辟内存空间,每次的操作系统开辟内存空间的时间程序无法掌控
      
         # 等待所有子进程结束
         for p in p_l:
            p.join()
         end_time = time.time()
         print('父进程',(end_time - start_time))
      
         '''
         打印机结果:
         主进程
         2 is running
         1 is running
         0 is running
         4 is running
         3 is running
         父进程 9.107510566711426
      
         Process finished with exit code 0
         '''
      

  1. 父进程操作子进程的其他属性(方法)

    from multiprocessing import Process
    import time
    import os
    
    # 子进程要执行的代码
    def task():
       print('子进程开始,子进程:%s,父进程:%s' %(os.getpid,os.ppid))
       time.sleep(3)
       print('子进程结束',x)
    
    # 开启子进程
    if __name__ == '__main__':
       print(os.getpid) #查看父进程的进程号
       p = Process(target=task)
       p.start()
       print(p.pid) # 查看子进程的进程号
       p.terminate() # 向操作系统发送结束子进程请求
       time.sleep(1)
       print(p.is_alive()) # 判断子进程是否存活,返回的是布尔值
    

  1. 僵尸进程与孤儿进程

    • 僵尸进程:僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。
    • 危害:僵尸进程占用进程号,如果存在过多的僵尸进程,很容易导致没有新的进程号提供给要产生的进程。一般来说父进程结束后会调用wait/waitpid来回收僵尸进程,如果父进程没有回收僵尸进程,则僵尸进程变成孤儿进程,然后由init进程进行回收。
    • 孤儿进程:在操作系统领域中,孤儿进程指的是在其父进程执行完成或被终止后仍继续运行的一类进程。这些孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

  1. 守护进程

    1. 守护进程(daemon)是一类在后台运行的特殊进程,用于执行特定的系统任务。如果父进程将子进程设置为守护进程,那么在父进程代码运行完毕后,注意,是父进程'代码运行完毕',而不是父进程结束,守护进程就立即被回收。

      For example:
      from multiprocessing import Process
      import time
      
      # 子进程代码
      def task(name):
         print('%s is running' %(name))
      
      # 父进程代码
      if __name__ == '__main__':
         obj = Process(target=task,args=('process',))
         obj.daemon = True # 将obj设置为守护进程,守护进程的特点是当父进程代码运行完毕后,无论自己的任务有没有完成,都随之结束。
         obj.start()
         print('父进程')
      

  1. 互斥锁

    • 在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为'互斥锁' 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。

    • 互斥锁和join的区别:

    • 二者原理一样,都是将并发变成串行,从而保证有序;

      • 区别一:join是按照人为指定的顺序执行,互斥锁是所有进程平等的竞争,谁先抢到谁执行。
      • 区别二:互斥锁可以让一部分代码(修改共享数据的代码)串行,而join只能将代码整体串行。
    • 互斥锁用法(区别一举例):

      from multiprocessing import Process,Lock
      
      lock = Lock()
      
      def task1(lock):
         lock.acquire() # lock.acquire()只能获取一次互斥锁
         print('task1 is running')
         lock.release() # lock.release()释放互斥锁,一定要在操作完毕后释放锁!
      
      def task2(lock):
         lock.acquire()
         print('task2 is running')
         lock.release()
      
      def task3(lock):
         lock.acquire()
         print('task3 is running')
         lock.release()
      
      if __name__ == '__main__':
         p1 = Process(target=task1,args=(lock,))
         p2 = Process(target=task2,args=(lock,))
         p3 = Process(target=task3,args=(lock,))
      
         p1.start()
         p2.start()
         p3.start()
      
    • 互斥锁应用场景之抢票(区别二举例):

      import os
      import json
      import time
      import random
      from multiprocessing import Process,Lock
      
      mutex_lock = Lock()
      
      def search_ticket():
         '''
         这是一个车票查询的操作
         :return:
         '''
         time.sleep(random.randint(1,3))
         with open('db.json', mode='r', encoding='utf-8') as f:
            dic = json.load(f)
            print(F'用户:{os.getpid()} 查询到剩余车票:{dic['count']}'
         return dic['count']
      def get_ticket():
         '''
         这是一个抢票的操作
         :return:
         '''
         with open('db.json', mode='r', encoding='utf-8') as f:
            time.sleep(random.randint(1, 3))
            dic = json.load(f)
         if dic['count'] > 0:
            dic['count'] -= 1
      
            with open('db.json', mode='w', encoding='utf-8') as f:
               json.dump(dic,f)
            print('用户:%s 购票成功!' %(os.getpid()))
         else:
            print('用户:%s 购票失败!' %(os.getpid()))
      
      def buy_ticket():
         '''
         这是一个购买车票的函数
         :return:
         '''
         search_ticket()
         with mutex_lock:
            get_ticket()
      
      
      if __name__ == '__main__':
         for i in range(10):
            p = Process(target = buy_ticket)
            p.start()
      

  1. IPC机制(Inter Process Communication)

    • IPC:进程间通信,进程间为什么要通信?因为进程需要协同完成工作,就涉及到一个进程要把自己处理的结果交给另外一个进程,而进程之间内存空间相互隔离,因此进程之间通信必须找到一种介质,该介质必须满足:

      • 是所有进程共享的;(实现进程共享的机制有管道和队列,队列本质是由管道+锁实现)
      • 必须是内存空间;
      • 帮我们处理好锁的问题。
    • 但凡涉及到进程之间通信,就使用Queue,队列是IPC机制实现的一种方式

      强调:

      • 队列是用来存进程之间通信消息的,数据量不应该过大;
      • Queue(maxsize) 中的maxsize的值超过内存限制就变得毫无意义;
      from multiprocessing import Queue # 从multiprocessing导入队列,队列特点:先进先出
      
      q = Queue(3,block=True) # maxsize=3,若不指定大小就无限大,受限于内存大小;block=True默认是阻塞。
      q.put('Hello')
      q.put({'name':'HC'})
      q.put(100)
      # q.put(55) 由于maxsize=3,第四个put会引起阻塞
      print(q.get())
      print(q.get())
      print(q.get())
      
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,245评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,749评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,960评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,575评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,668评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,670评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,664评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,422评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,864评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,178评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,340评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,015评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,646评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,265评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,494评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,261评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,206评论 2 352

推荐阅读更多精彩内容

  • 进程 操作系统背景知识 顾名思义,进程即正在执行的一个过程。进程是对正在运行程序的一个抽象。 进程的概念起源于操作...
    go以恒阅读 943评论 0 2
  • 写在前面的话 代码中的# > 表示的是输出结果 输入 使用input()函数 用法 注意input函数输出的均是字...
    FlyingLittlePG阅读 2,753评论 0 8
  • 线程 操作系统线程理论 线程概念的引入背景 进程 之前我们已经了解了操作系统中进程的概念,程序并不能单独运行,只有...
    go以恒阅读 1,638评论 0 6
  • 多进程 要让python程序实现多进程,我们先了解操作系统的相关知识。 Unix、Linux操作系统提供了一个fo...
    蓓蓓的万能男友阅读 595评论 0 1
  • 进程、进程的使用、进程注意点、进程间通信-Queue、进程池Pool、进程与线程对比、文件夹拷贝器-多任务 1.进...
    Cestine阅读 798评论 0 0