zoukankan      html  css  js  c++  java
  • PythonStudy——多进程编程

    进程

    一个正在被运行的程序就称之为进程,是程序具体执行过程,一种抽象概念

    进程来自于操作系统

    多进程

    进程和程序的区别

    程序就是一堆计算机可以识别文件,程序在没有被运行就是躺在硬盘上的一堆二进制

    运行程序时,要从硬盘读取数据到内存中,CPU从内存读取指令并执行 ,

    一旦运行就产生了进程

    一个程序可以多次执行 产生多个进程,但是进程之间相互独立

    当我们右键运行了一个py文件时 ,其实启动的是python解释器,你的py文件其实是当作参数传给了解释器

    阻塞 非阻塞 并行 并发 (重点)

    阻塞 : 程序遇到io操作是就进入了阻塞状态

    本地IO input print sleep read write

    网络IO recv send

    非阻塞: 程序正常运行中 没有任何IO操作 就处于非阻塞状态

    阻塞 非阻塞 说的是程序的运行状态

    并发: 多个任务看起来同时在处理 ,本质上是切换执行 速度非常快

    并行: 多个任务真正的同时执行 必须具备多核CPU 才可能并行

    并发 并行 说的是 任务的处理方式

    三种状态的切换

    程序员永恒的话题

    提高效率

    根本方法就是让程序尽可能处于运行状态

    减少IO 尽可能多占用CPU时间

    缓冲区就是用于减少IO操作的

    进程的创建以及销毁 了解

    菜谱: 就是程序

    做菜的过程:就是进程

    进程的两种使用方式 (重点)

    1.直接实例化Process ,将要执行任务用target传入

     

    2.继承Process类 ,覆盖run方法 将任务放入run方法中

    import os
    from multiprocessing import  Process
    class MyProcess(Process):
       def __init__(self,name):
           super().__init__()
           self.name = name
       # 继承Procee覆盖run方法将要执行任务发到run中
       def run(self):
           print(self.name)
           print("子进程 %s running!" % os.getpid())
           print("子进程 %s over!" % os.getpid())

    if __name__ == '__main__':
       # 创建时 不用再指定target参数了
       p = MyProcess("rose")
       p.start()
       print("父进程over!")

    join函数 (重点)

    Process的对象具备一个join函数

    用于提高子进程优先级 ,使得父进程等待子进程结束

    僵尸与孤儿进程 了解

    孤儿进程

    指的是,父进程先结束 ,而子进程还在运行着,

    孤儿进程无害,有 其存在的必要性

    例如:qq开启了浏览器,qq先退出了 浏览器应该继续运行

    孤儿进程会被操作系统接管

    僵尸进程

    值得是,子进程已经结束了,但是操作系统会保存一些进程信息,如PID,运行时间等,此时这个进程就称之为僵尸进程

    僵尸进程如果太多将会占用大量的资源,造成系统无法开启新新进程

    linux 中有一个wai/waitpid 用于父进程回收子进程资源

    python会自动回收僵尸进程

    常用属性

    # p.join() # 等待子进程结束
    # p.terminate() # 终止进程
    # print(p.name) # 进程的名称
    # print(p.is_alive()) #是否存活
    # p.terminate() # 与start一样 都是给操作系统发送指令 所以会有延迟
    # print(p.is_alive())
    # print(p.pid)
    # print(p.exitcode) # 获取退出码

    守护进程

        什么是守护进程

    ​    进程是一个正在运行的程序

    ​    守护进程也是一个普通进程

    ​    意思是一个进程可以守护另一个进程

    例如

    ​ 康熙要是一个进程的话,后宫佳丽都是守护者

    ​ 如果康熙挂了, 后宫佳丽们要陪葬

    结论:

    如果b是a的守护进程,a是被守护的进程,a要是挂了,b也就随之结束了

    测试:

    from multiprocessing import Process
    import time
    
    # 妃子的一生
    def task():
    print("入宫了.....")
    time.sleep(50)
    print("妃子病逝了......")
    
    
    if __name__ == '__main__':
    # 康熙登基了
    print("登基了.....")
    
    # 找了一个妃子
    p = Process(target=task)
    
    # 设置为守护进程 必须在开启前就设置好
    p.daemon = True
    p.start()
    
    # 康熙驾崩了
    time.sleep(3)
    print("故事结束了!")

    使用场景:

    ​ 父进程交给了子进程一个任务,任务还没有完成父进程就结束了,子进程就没有继续执行的意义了

    ​ 例如:qq 接收到一个视频文件,于是开启了一个子进程来下载,如果中途退出了qq,下载任务就没必须要继续运行了

    互斥锁

    什么是互斥锁

    互斥锁 互相排斥的锁,我在这站着你就别过来,(如果这个资源已经被锁了,其他进程就无法使用了)

    需要强调的是: 锁 并不是真的把资源锁起来了,只是在代码层面限制你的代码不能执行

    为什么需要互斥锁:

    并发将带来资源的竞争问题
    当多个进程同时要操作同一个资源时,将会导致数据错乱的问题
    如下列所示:

    解决方案1:

    ​ 加join,
    ​ 弊端 1.把原本并发的任务变成了穿行,避免了数据错乱问题,但是效率降低了,这样就没必要开子进程了
    ​ 2.原本多个进程之间是公平竞争,join执行的顺序就定死了,这是不合理的

    解决方案2:

    ​ 就是给公共资源加锁,互斥锁
    ​ 互斥锁 互相排斥的锁,我在这站着你就别过来,(如果这个资源已经被锁了,其他进程就无法使用了)

    锁 并不是真的把资源锁起来了,只是在代码层面限制你的代码不能执行

    锁和join的区别:​

    1. join是固定了执行顺序,会造成父进程等待子进程

    ​ 锁依然是公平竞争谁先抢到谁先执行,父进程可以做其他事情

    ​ 2.最主要的区别:
    ​ join是把进程的任务全部串行
    ​ 锁可以锁任意代码 一行也可以 可以自己调整粒度


    案例

    from multiprocessing import Process,Lock
    import time,random
    
    def task1(lock):
    # 要开始使用了 上锁
    lock.acquire() #就等同于一个if判断
    print("hello iam jerry")
    time.sleep(random.randint(0, 2))
    print("gender is boy")
    time.sleep(random.randint(0, 2))
    print("age is 15")
    # 用完了就解锁
    lock.release()
    
    def task2(lock):
    lock.acquire()
    print("hello iam owen")
    time.sleep(random.randint(0,2))
    print("gender is girl")
    time.sleep(random.randint(0,2))
    print("age is 48")
    lock.release()
    
    def task3(lock):
    lock.acquire()
    print("hello iam jason")
    time.sleep(random.randint(0,2))
    print("gender is women")
    time.sleep(random.randint(0,2))
    print("age is 26")
    lock.release()
    
    if __name__ == '__main__':
    lock = Lock()
    
    p1 = Process(target=task1,args=(lock,))
    p2 = Process(target=task2,args=(lock,))
    p3 = Process(target=task3,args=(lock,))
    
    p1.start()
    # p1.join()
    
    p2.start()
    # p2.join()
    
    p3.start()
    # p3.join()
    
    # print("故事结束!")
    
    # 锁的伪代码实现
    
    # if my_lock == False:
    # my_lock = True
    #    #被锁住的代码
    my_lock = False 解锁

    注意1: 不要对同一把执行多出acquire 会锁死导致程序无法执行 一次acquire必须对应一次release

    l = Lock()
    l.acquire()
    print("抢到了!")
    l.release()
    l.acquire()
    print("强哥毛线!")

    注意2:想要保住数据安全,必须保住所有进程使用同一把锁

    IPC

    ​ 进程间通讯

    ​ 通讯指的就是交换数据

    ​ 进程之间内存是相互隔离的,当一个进程想要把数据给另外一个进程,就需要考虑IPC

    方式

    ​ 管道

           只能单向通讯,数据都是二进制

    ​ 文件

           在硬盘上创建共享文件

    ​ 缺点:速度慢

    ​ 优点:数据量几乎没有限制

    ​socket

    ​ 编程复杂度较高

    ​ 共享内存:必须由操作系统来分配 要掌握的方式*****

    ​ 优点: 速度快

    ​ 缺点: 数据量不能太大

    共享内存的方式

    Manager类

    ​ Manager提供很多数据结构 list dict等等

    ​ Manager所创建出来的数据结构,具备进程间共享的特点

    from multiprocessing import Process,Manager,Lock
    import time
    
    
    def task(data,l):
    l.acquire()
    num = data["num"] #
    time.sleep(0.1)
    data["num"] = num - 1
    l.release()
    
    if __name__ == '__main__':
    # 让Manager开启一个共享的字典
    m = Manager()
    data = m.dict({"num":10})
    
    l = Lock()
    
    for i in range(10):
    p = Process(target=task,args=(data,l))
    p.start()
    
    time.sleep(2)
    print(data)

    ​ 需要强调的是 Manager创建的一些数据结构是不带锁的 可能会出现问题

    Queue队列

    帮我们处理了锁的问题 重点

    ​ 队列是一种特殊的数据结构,先存储的先取出 就像排队 先进先出

    ​ 相反的是堆栈,先存储的后取出, 就像衣柜 桶装薯片 先进后出

    ​ 扩展:

    ​ 函数嵌套调用时 执行顺序是先进后出 也称之为函数栈

    ​ 调用 函数时 函数入栈 函数结束就出栈

    from multiprocessing import Queue
    # 创建队列 不指定maxsize 则没有数量限制
    q = Queue(3)
    # 存储元素
    # q.put("abc")
    # q.put("hhh")
    # q.put("kkk")
    
    # print(q.get())
    # q.put("ooo") # 如果容量已经满了,在调用put时将进入阻塞状态 直到有人从队列中拿走数据有空位置 才会继续执行
    
    #取出元素
    # print(q.get())# 如果队列已经空了,在调用get时将进入阻塞状态 直到有人从存储了新的数据到队列中 才会继续
    
    # print(q.get())
    # print(q.get())
    
    
    #block 表示是否阻塞 默认是阻塞的 # 当设置为False 并且队列为空时 抛出异常
    q.get(block=True,timeout=2)
    # block 表示是否阻塞 默认是阻塞的 # 当设置为False 并且队列满了时 抛出异常
    # q.put("123",block=False,)
    # timeout 表示阻塞的超时时间 ,超过时间还是没有值或还是没位置则抛出异常 仅在block为True有效

    生产者消费者模型 重点

    ## 是什么

    ​ 模型 就是解决某个问题套路

    ​ 产生数据的一方称之为生产者

    ​ 处理数据的一方称之为消费者

    ​ 例如: 爬虫 生活中到处都是这种模型

    ​ 饭店 厨师就是生产者 你吃饭的人就是消费者

    ## 生产者和消费者出啥问题了? 解决什么问题

    ​ 生产者和消费,处理速度不平衡,一方快一方慢,导致一方需要等待另一方

    ## 生产者消费者模型解决这个问题的思路: 怎么解决

    ​ 原本,双方是耦合 在一起,消费必须等待生产者生成完毕在开始处理, 反过来

    ​ 如果消费消费速度太慢,生产者必须等待其处理完毕才能开始生成下一个数据

    ### 解决的方案:

    ​ 将双方分开来.一专门负责生成,一方专门负责处理

    ​ 这样一来数据就不能直接交互了 双方需要一个共同的容器

    ​ 生产者完成后放入容器,消费者从容器中取出数据

    ​ 这样就解决了双发能力不平衡的问题,做的快的一方可以继续做,不需要等待另一方

    案例:

    def eat(q):
    for i in range(10):
    # 要消费
    rose = q.get()
    time.sleep(random.randint(0, 2))
    print(rose,"吃完了!")
    
    # 生产任务
    def make_rose(q):
    for i in range(10):
    # 再生产
    time.sleep(random.randint(0, 2))
    print("第%s盘青椒肉丝制作完成!" % i)
    rose = "第%s盘青椒肉丝" % i
    # 将生成完成的数据放入队列中
    q.put(rose)
    
    if __name__ == '__main__':
    # 创建一个共享队列
    q = Queue()
    make_p = Process(target=make_rose,args=(q,))
    eat_p = Process(target=eat,args=(q,))
    
    
    make_p.start()
    eat_p.start()
  • 相关阅读:
    解释机器学习模型的一些方法(一)——数据可视化
    机器学习模型解释工具-Lime
    Hive SQL 语法学习与实践
    LeetCode 198. 打家劫舍(House Robber)LeetCode 213. 打家劫舍 II(House Robber II)
    LeetCode 148. 排序链表(Sort List)
    LeetCode 18. 四数之和(4Sum)
    LeetCode 12. 整数转罗马数字(Integer to Roman)
    LeetCode 31. 下一个排列(Next Permutation)
    LeetCode 168. Excel表列名称(Excel Sheet Column Title)
    论FPGA建模,与面向对象编程的相似性
  • 原文地址:https://www.cnblogs.com/tingguoguoyo/p/10957477.html
Copyright © 2011-2022 走看看