并发编程2 进程与互斥锁

88 阅读5分钟

创建进程的多种方式

1.通过鼠标栓剂程序
2.通过代码创建进程(掌握两种)
      创建进程的代码在不同的操作系统下,底层原理是有区别的
          在windos中创建进程类似于导入模块,使用启动脚本if __name__ == '__main__': 
          在linux和mac中创建进程类似于拷贝,不需要脚本启动,也可以使用脚本启动
   第一种:
        from multiprocessing import Process
        import time

        def task(name):
            print(f'{name}运行开始')
            time.sleep(3)
            print(f'{name}运行结束')

        if __name__ == '__main__':
            p = Process(target=task,args=('hanser',))    # 创建一个进程对象
            p.start()                                    # 告诉操作系统创建一个对象(异步操作)
            print('主进程')

   第二种:   
        from multiprocessing import Process
        import time

        class MyProcess(Process):
            def __init__(self, name):
                super().__init__()
                self.name = name

            def run(self):
                print(f'{self.name}运行开始')
                time.sleep(3)
                print(f'{self.name}运行结束')
        if __name__ == '__main__':
            obj = MyProcess('tom')
            obj.start()
            print('主进程')

join方法

  • 如何让主程序在子程序运行结束后在执行?
    • 1.直接在代码中添加time.sleep()
      由于我们无法准确获取子进程的执行时间,所以这样做显然是不合理的
      
    • 2.使用join方法(合理)
     from multiprocessing import Process
     import time
    
     def task(name,n):
         print(f'{name}正在运行')
         time.sleep(n)
         print(f'{name}运行结束')
     if __name__ == '__main__':
         p1 = Process(target=task,args=('hanser', 1))    # args就是通过元组的形式给函数传参
         p2 = Process(target=task, args=('tom', 2))
         p3 = Process(target=task, args=('kunkun', 3))
         start_time = time.time()    
         p1.start()        # p1.start()
         p2.start()        # p1.join()
         p3.start()        # p2.start()
         p1.join()         # p2.start()
         p2.join()         # p3.start()
         p3.join()         # p3.join() 
             # join放的位置不一样,多任务下等待目标就不一样
         end_time = time.time() - start_time
         print('总耗时:%s' % end_time)
         print('主进程')
    

进程间的数据默认隔离

  • 多个进程数据之间彼此之间是默认互相隔离的
    对多个进程的数据进行交互需要借助'管道'或者'队列'
    
     from multiprocessing import Process
    
     num = 19800
    
     def task():
         global num
         num = 200
         print('子进程打印的num',num)   # 200
     if __name__ == '__main__':
         p = Process(target=task)
         p.start()
         p.join()
         print('父进程打印的num',num)   # 19800
              # 此时父进程中的money,并没有被改变
    

进出间通信(IPC机制)、

  • 消息队列
队列:  先进先出
from multiprocessing import Queue

    # 1.创建队列对象
q = Queue(3)     # 括号内指定队列可以容纳的数据个数 默认:2147483647

    # 2.往队列添加数据
q.put(111)
print(q.full())  # 判断队列是否已经存满   False
q.put(222)
q.put(333)  
print(q.full())  # True
q.put(444)       # 超出数据存放极限 那么程序一致处于阻塞态 直到队列中有数据被取出
    # 3.从队列中取数据
print(q.get_nowait())
print(q.get_nowait())
print(q.get_nowait())   # 队列中有数据可取  不会报错
print(q.get_nowait())   # 队列中无数据可取  直接报错
  
print(q.get())          # 超出数据获取极限 那么程序一致处于阻塞态 直到队列中有数据被添加
print(q.empty())        # 判断队列是否已经空了
   """
       q.full()
       q.empty()
       q.get_nowait()
       上述方法在多进程下不能准确使用(失效)!!!
   """
  • IPC机制
    • 1.主进程与子进程通信
      from multiprocessing import Queue, Process
      
      def procedure(q):
          print('来自子进程procedure的方法往队里中添加了数据',q.get())
      
      if __name__ == '__main__':
          q = Queue()
          q.put('主进程代码往队列里添加了的数据')
          p = Process(target=procedure, args=(q,))
          p.start()
          p.join()
          print('主进程')
      
    • 2.子进程与子进程通信
      from multiprocessing import Queue, Process
      
      def procedure(q):
          q.put('子进程procedure往队里中添加了数据')
      def consumer(q):
          print('子进程的consumer从队列中获取数据', q.get())
      if __name__ == '__main__':
          q = Queue()  # 在主进程中产生q对象 确保所有的子进程使用的是相同的q
          p1 = Process(target=procedure, args=(q,))
          p2 = Process(target=consumer, args=(q,))
          p1.start()
          p2.start()
          print('主进程')
      

生产者消费者模型

  • 生产者是产生数据的(获取数据)
  • 消费者是处理数据的(筛选数据)
    完整的生产者消费者模型至少有三个部分
        1.生产者              2.消息队列                3.消费者
    

进程相关方法

    1. 查看进程号
# 使用进程的方法需要导入currrnt_process模块
from multiprocessing import current_process  
import os

current_process().pid # 获取当前进程的PID
os.getpid()           # 获取当前进程的进程号
os.getppid            # 获取当前进程的父进程的进程号
    1. 销毁子进程
   .terminate()
    1. 判断进程是否存活
   .is_alive()

守护进程

  • 伴随着守护对象的存活或存活,死亡而死亡
     from multiprocessing import Process
     import time
    
     def task(name):
         print('守卫:%s存活' % name)
         time.sleep(3)
         print('守卫:%s被刀' % name)
    
     if __name__ == '__main__':
         p = Process(target=task, args=('1',))
         p.daemon = True  
             # 此代码将子进程设置为守护进程:主进程代码结束,子进程立刻结束。且必须在start之前执行
         p.start()
         print('预言家被刀出局!!!')
    

互斥锁

  • 日常12306买票流程是
    1.查票
    2.买票 >>>:  1.查票    2.买票 
       # 会在进行购买的时候再次查询一下当前的票量剩余
       
       
    # 可以通过代码模拟一下
     1.先创建一个json问价,设置一个字典模拟一下票的余量
     2.创建py文件夹编写代码
     from multiprocessing import Process
     import time
     import json
     import random
    
    
       # 1.查票
     def search(name):
         with open(r'data.json', 'r', encoding='utf8') as f:
             data = json.load(f)
         print('%s在查票 当前余票为:%s' % (name, data.get('ticket_num')))
    
    
       # 2.买票
     def buy(name):
           # 再次确认票
         with open(r'data.json', 'r', encoding='utf8') as f:
             data = json.load(f)
           # 模拟网络延迟
         time.sleep(random.randint(1, 3))
           # 判断是否有票 有就买
         if data.get('ticket_num') > 0:
             data['ticket_num'] -= 1
             with open(r'data.json', 'w', encoding='utf8') as f:
                 json.dump(data, f)
             print('%s买票成功' % name)
         else:
             print('%s很倒霉 没有抢到票' % name)
     def run(name):
         search(name)
         buy(name)
    
     if __name__ == '__main__':
         for i in range(10):
             p = Process(target=run, args=('用户%s'%i, ))
             p.start()
    

僵尸进程和孤儿进程

  • 僵尸进程
    • 进程已经运行结束 但是相关的资源并没有完全清空
      • 需要父进程参与回收
  • 孤儿进程
    • 父进程意外死亡 子进程正常运行 该子进程就称之为孤儿进程
      • 孤儿进程也不是没有人管 操作系统会自动分配福利院接收