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()
  • 相关阅读:
    [LeetCode] Sqrt(x)
    [LeetCode] Rotate Array
    【经典算法】贪心算法
    【经典算法——查找】二分查找
    ARP(Adress Resolution Protocol): 地址解析协议
    【经典算法】分治策略
    [LeetCode] Recover Binary Search Tree
    [LeetCode] Convert Sorted Array to Binary Search Tree
    python数据类型之字典(dict)和其常用方法
    简单了解hash
  • 原文地址:https://www.cnblogs.com/tingguoguoyo/p/10957477.html
Copyright © 2011-2022 走看看