• python之并发编程-进程之间的通信


    进程之间的通信

    1. 互斥锁

      进程之间数据不共享,但共享同一套文件系统,或同一个打印终端是没有问题的。但共享导致了竞争,若不加以控制就会造成错乱。如下:

      from multiprocessing import Process
      import time
      import random
      def task1():
          print('task1开始打印')
          time.sleep(random.randint(1,3))
          print('task1打印完成')
      def task2():
          print('task2开始打印')
          time.sleep(random.randint(1,3))
          print('task2打印完成')
      def task3():
          print('task3开始打印')
          time.sleep(random.randint(1,3))
          print('task3打印完成')
      if __name__ == '__main__':
          p1 = Process(target=task1,)
          p2 = Process(target=task2,)
          p3 = Process(target=task3,)
          p1.start()
          p2.start()
          p3.start()
      # task1开始打印
      # task2开始打印
      # task3开始打印
      # task1打印完成
      # task3打印完成
      # task2打印完成
      

      使用加锁可以使并发变成串行,牺牲效率,避免竞争

      from multiprocessing import Process
      from multiprocessing import Lock
      import time
      import random
      def task1(lock):
          lock.acquire()
          print('task1开始打印')
          time.sleep(random.randint(1,3))
          print('task1打印完成')
          lock.release()
      def task2(lock):
          lock.acquire()
          print('task2开始打印')
          time.sleep(random.randint(1,3))
          print('task2打印完成')
          lock.release()
      def task3(lock):
          lock.acquire()
          print('task3开始打印')
          time.sleep(random.randint(1,3))
          print('task3打印完成')
          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()
      # task1开始打印
      # task1打印完成
      # task2开始打印
      # task2打印完成
      # task3开始打印
      # task3打印完成
      

      利用多进程抢票:

      from multiprocessing import Process
      from multiprocessing import Lock
      import time
      import random
      import json
      def search():
          time.sleep(random.random())	# 模拟读取数据网络延迟
          with open('db.json',encoding='utf-8') as f:
          	dic = json.load(f)
      	print(f'剩余票数{dic["count"]}')
      def get():
          with open('db.json',encoding='utf-8') as f:
              dic = json.load(f)
          time.sleep(random.random())	# 模拟读取数据网络延迟
          if dic['count'] > 0:
              dic['count'] -= 1
              time.sleep(random.random())	# 模拟写数据网络延迟
              with open('db.json',encoding='utf-8',mode='w') as f:
                  json.dump(dic,f)
              print(f'{os.getpid()}用户购买成功')
          else:
              print('票没了')
      def task(lock):
          search()
          lock.acquire()
          get()
          lock.release()
      if __name__ == '__main__':
          lock = Lock()
          for i in range(5):
          	p = Process(target=task,args=(lock,))
          	p.start()
      # 剩余票数1
      # 剩余票数1
      # 14004用户购买成功
      # 剩余票数0
      # 剩余票数0
      # 票没了
      # 剩余票数0
      # 票没了
      # 票没了
      # 票没了
      

      虽然可以用文件共享数据实现进程间通信,但问题是:

      1. 效率低(共享数据居于文件,而文件是硬盘上的数据)
      2. 需要自己加锁处理,容易形成死锁、递归锁(注意一个进程只能使用一个锁)
    2. 队列

      multiprocessing模块支持两种形式:队列和管道,这两种方式都是传递数据的。

      队列就是存在于内存中的一个容器,最大的特点就是FIFO,完全支持先进先出原则

      1. 创建队列的类(底层就是以管道和索的方式实现的)

        Queue([maxsize]):创建共享的进程队列,Queue是多进程安全队列,可以使用Queue实现多进程之间的数据传递

      2. 参数介绍

        maxsize是队列中允许最大项数,省略则无大小限制

      3. 方法介绍

        • 主要方法

          # q.put方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。如果超时,会抛出Queue.Full异常。如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常。
          # q.get方法可以从队列读取并且删除一个元素。同样,get方法有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。如果blocked为False,有两种情况存在,如果Queue有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常.  
          # q.get_nowait():同q.get(False)
          # q.put_nowait():同q.put(False) 
          # q.empty():调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。
          # q.full():调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中的项目被取走。
          # q.qsize():返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样
          
        • 其他方法

          # q.cancel_join_thread():不会在进程退出时自动连接后台线程。可以防止join_thread()方法阻塞
          # q.close():关闭队列,防止队列中加入更多数据。调用此方法,后台线程将继续写入那些已经入队列但尚未写入的数据,但将在此方法完成时马上关闭。如果q被垃圾收集,将调用此方法。关闭队列不会在队列使用者中产生任何类型的数据结束信号或异常。例如,如果某个使用者正在被阻塞在get()操作上,关闭生产者中的队列不会导致get()方法返回错误。
          # q.join_thread():连接队列的后台线程。此方法用于在调用q.close()方法之后,等待所有队列项被消耗。默认情况下,此方法由不是q的原始创建者的所有进程调用。调用q.cancel_join_thread方法可以禁止这种行为
          
    3. 进程之间的通信实例

      利用队列进程之间通信:简单,方便,不用自己动手加锁。队列自带阻塞,可持续化取数据。

      # 用队列来存储网络请求,模拟网购抢购
      import os
      from multiprocessing import Queue
      from multiprocessing import Process
      def task(q):
          try:
              q.put(f'{os.getpid()}',block=False)	# 模拟存放请求
          except Exception:
              return
      if __name__ == '__main__':
          q = Queue(10)
          for i in range(100):
              p = Process(target=task,args=(q,))
              p.start()
          for i in range(1,11):
              print(f'排名第{i}的用户:{q.get()}')	# 获取前十个请求
      
    4. 生产者与消费者模型

      生产者:生产数据进程

      消费者:对生产者生产出来的数据作进一步处理的进程

      生产者与消费者之间需要有一个容器作为缓冲区,平衡生产力与消费力

      生产者消费者模型多用于并发

      from multiprocessing import Process
      from multiprocessing import Queue
      import time
      import random
      def producer(name,q):
          for i in range(1,6):
              time.sleep(random.randint(1,3))
              ret = f'{i}号包子'
              q.put(ret)
              print(f'33[0;32m 生产者{name}:生产了{ret}33[0m')
      def consumer(name,q):
          while 1:
              try:
                  time.sleep(random.randint(1,3))
                  ret = q.get(timeout=5)
                  print(f'消费者{name}:吃了{ret}')
              except Exception:
                  return
      if __name__ == '__main__':
          q = Queue()
          p1 = Process(target=producer,args=('铁憨憨',q))
          p2 = Process(target=consumer,args=('皮皮寒',q))
          p1.start()
          p2.start()
      # 生产者铁憨憨:生产了1号包子
      #消费者皮皮寒:吃了1号包子
      # 生产者铁憨憨:生产了2号包子
      #消费者皮皮寒:吃了2号包子
      # 生产者铁憨憨:生产了3号包子
      # 生产者铁憨憨:生产了4号包子
      #消费者皮皮寒:吃了3号包子
      # 生产者铁憨憨:生产了5号包子
      #消费者皮皮寒:吃了4号包子
      #消费者皮皮寒:吃了5号包子
      
    5. 总结

      进程之间的通信:

      1. 基于文件+ 锁的形式: 效率低,麻烦.
      2. 基于队列: 推荐使用形式.
      3. 基于管道: 管道自己加锁, 底层可以会出现数据丢失损坏.

      多个进程抢占一个资源: 串行,有序以及数据安全.

      多个进程实现并发的效果: 生产者消费者模型.

  • 相关阅读:
    JavaScript 金字塔
    最短路径—Dijkstra算法和Floyd算法
    Qt编程的一些技巧
    Qt-Creator 加入qwt库
    关于usr/bin/ld: cannot find -lxxx问题总结(Qt编译错误cannot find -lGL)
    根文件系统制作、NFS配置与安装及利用NFS挂载根文件系统
    tslib1.4与Qt4.8.6的交叉编译与移植
    用树莓派做3G无线路由器
    python学习笔记6:面向对象
    pyhton学习笔记5:常用模块:datatime,random,json,re
  • 原文地址:https://www.cnblogs.com/yaoqi17/p/11240298.html
Copyright © 2020-2023  润新知