• 互斥锁、共享内存方式以及生产者消费者模型


    守护进程

    1、守护进程的概念

      进程指的是一个正在运行的程序,守护进程也是一个普通进程

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

    import time
    from multiprocessing import Process
    
    def task():
        print("子进程   走起!!!")
        time.sleep(5)
        print("子进程  挂掉了")
    
    if __name__ == '__main__':
        print("主进程   走起!!!")
    
        p = Process(target=task)
        p.daemon = True  # 将子进程设置为守护进程
        p.start()
        time.sleep(3)
        print("主进程  挂掉了")
    # --1)如果设置为守护进程,那么主进程一旦结束,那么子进程立马也结束(代码终止执行)
    # --2)如果没有设置为守护进程,那么主进程结束执行完代码之后,子进程还会继续执行子进程里面的代码

    2、结论

      如果a 是 b 的守护进程,那么 b 就是被守护的进程, b要是(代码运行完毕)死  ,a也会跟着死

      守护进程在主进程代码运行结束之后就死了

    3、使用场景

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

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

    互斥锁(重点)

    为什么需要互斥锁

      当我们同时开启几个子进程的时候,而这几个子进程又要同时操作一个资源(文件或者控制台),

      将会导致数据的错乱问题(谁先抢到谁打印,数据混乱看不懂)

    解决方案1:

      加join    (就是每开一个子进程,就加一个join,限制后面的子进程不能开启)

      弊端:

        --1、把原本并发的任务变成了串行,避免了数据错乱问题,但是效率降低了,

          而且是执行完第一个子进程才开启第二个子进程,都放在主程序也是同样效果,没必要开子进程

        --2、原本多个进程之间公平竞争,join执行的顺序是定死的,不合理

    解决法案2:

      给公共资源加锁(互斥锁)

        互斥锁,字面理解就是互相排斥的锁,在程序中指的是,如果这个资源已经被锁住了,其他进程就无法使用

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

    import time,random
    from multiprocessing import Process,Lock
    
    def task1(lock):
        lock.acquire()      # 上锁
        print('任务一  走起')
        time.sleep(random.randint(0,2))
        print('任务一  结束')
        lock.release()      # 解锁
    
    def task2(lock):
        lock.acquire()
        print('任务二  走起')
        time.sleep(random.randint(0,2))
        print('任务二  结束')
        lock.release()
    
    def task3(lock):
        lock.acquire()
        print('任务三  走起')
        time.sleep(random.randint(0,2))
        print('任务三  结束')
        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()
        p2.start()
        p3.start()

      注意:

        --1、不要对同一代码执行多次acquire,会锁死程序无法执行,一次acquire必须对应一次release

            (一次加锁相当于在代码做一次判断,判断成功改状态,再加锁又相当于做一次判断,判断就会失败,锁死)

        --2、想要保证数据安全,必须保证所有进程使用同一把锁

           (如果每个进程拿到的锁都不一样,那么判断的条件就不会同步)

    锁和join的区别

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

        锁依然是公平竞争,谁先抢到谁先执行,父进程也可以执行其他任务

      2、最主要的区别:

        join是把进程的所有任务全部串行

        锁可以所以任意代码,一行也可以,可以自己调整粒度  粒度(指的是被锁住代码的大小,越小效率越快)

    IPC

      IPC就是进程间通讯,通讯指的是交换数据

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

    IPC方式:

      管道:只能单向通讯(一边读,一边写),数据都是二进制

      文件:在硬盘上创建共享文件

          优点:硬盘的空间大,所以共享的数据量几乎没有限制

          缺点:硬盘读取速度慢

      socket:编程复杂度高

      共享内存: 必须由操作系统来分配     (必须掌握)

        优点:速度快

        缺点:数据量不能太大

    共享内存的方式

    1、Manager类

      Manager提供了很多数据结构,如list,dict等等(可以在源码中看到)

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

    from multiprocessing import Manager,Process,Lock
    import time
    
    def task(dic,lock):
        lock.acquire()  # 这里如果不加锁的话,可能出现多个进程刚取出来的值都是100,然后修改完都是99
        nums = dic["nums"]
        # time.sleep(2)  # 所有进程拿到100睡2秒,最后修改的数据就是99
        dic["nums"] = nums - 1
        lock.release()
    if __name__ == '__main__':
        lock = Lock()
        m = Manager()    # 创建一个manager对象
        dic = m.dict({"nums":100})    # 括号里需要的是一个映射关系,帮我们生成一个进程间共享的字典
        for i in range(5):
            p = Process(target=task,args=(dic,lock))
            p.start()
        time.sleep(10)
        print(dic["nums"])

    需要注意的是:manager创建的一些数据结构是不带锁的,可能会出现同时修改一个数据的情况,会出问题,不推荐使用、

    2、Queue队列 帮我们处理了锁的问题 (重点)

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

      相反的就是堆栈,先存取的后取出,就像把衣服叠进箱子,取的时候先取后叠进去的

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

    from multiprocessing import Queue
    
    q = Queue(maxsize=3)  # 创建一个队列,里面的参数表示的是队列的容量,如果不写默认无限大
    
    # 存数据 q.put() 放进去
    q.put("a")
    q.put("b")
    q.put("c")
    # q.put("d")
    # 如果队列里面已经满了,你还继续往里放,他就会进入阻塞状态,一直等到队列有空位置把数据放进去
    
    # 取数据 q.get() 拿出来
    print(q.get())
    print(q.get())
    print(q.get())
    # print(q.get())
    # 同样的,如果队列里面已经空了,你还继续取,它也会进入阻塞状态,一直等到队列里有数据然后取出来
    
    q.get(block=True,timeout=2)
    # 里面block参数表示的是是否阻塞,默认True(阻塞),当设置为False时,并且队列为空时会立马抛出异常
    q.put("aaa",block=True,timeout=2)
    # 里面block参数表示的是是否阻塞,默认True(阻塞),当设置为False时,并且队列为满时会立马抛出异常
    # timeout 表示的时阻塞的超时时间,超过时间还是没有位置或着空的话就会抛出异常

    生产者消费者模型 (重点)

    1、概念

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

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

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

      例如:饭店厨师就是生产者,吃饭的人就是消费者

    2、生产者和消费者产生的问题

      生产者和消费者,处理速度不平衡,一方快一方慢,导致一方需要等待另一方再能接着往下执行

    3、生产者消费者模型解决这个问题的思路

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

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

    4、解决的方案

      将双方分开来,一方专门,负责生产,一方专门负责处理

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

      这样就解决了双方能力不平衡的问题,做的快的一方可以继续做,不需要等待另一方,提高了整体的运行效率

    from multiprocessing import Process,Queue
    import time,random
    def eater(q):
        for i in range(5):
            time.sleep(random.randint(0, 2))
            food = q.get()
            print("%s吃完了"%food)
    
    def cooker(q):
        for i in ["包子","骨头","","","刀子"]:
            time.sleep(random.randint(0, 2))
            q.put(i)
            print("%s做好了"%i)
    
    if __name__ == '__main__':
        q = Queue()
        cp = Process(target=cooker,args=(q,))
        ep = Process(target=eater,args=(q,))
        cp.start()
        ep.start()
  • 相关阅读:
    使用thinkphp连接sqlserver数据库时提示“系统不支持:sqlsrv”
    ThinkPHP连接sql server数据库
    帝国cms常用变量总结
    帝国CMS常见问题记录
    帝国CMS商城功能高级使用
    帝国CMS 6.0功能解密之新版结合项功能,帝国结合项使用
    CSS媒体查询,CSS根据不同的分辨率显示不同的样式
    使用phpstuby时,Apache或mysql无法启动,端口被占用
    火狐firefox提示“内容编码错误 无法显示您尝试查看的页面,因为它使用了无效或者不支持的压缩格式。”
    js跳转页面方法整理
  • 原文地址:https://www.cnblogs.com/hesujian/p/10968848.html
Copyright © 2020-2023  润新知