当前位置: 欣欣网 > 码农

Python升级之路( Lv30) 并发编程三剑客(下)

2024-05-13码农

Python系列文章目录


第二十五章 Pygame游戏开发基础(下)

第二十六章 基于pygame 实现的坦克大战

第二十七章 并发编程初识

第二十八章 并发编程三剑客(上)

第二十九章 并发编程三剑客(中)

第三十章 并发编程三剑客(下)


  • Python系列文章目录

  • 前言

  • 协程

  • 协程与线程的比较

  • asyncio实现协程(重点)


  • 前言

    大家好, 我是了不起, 欢迎收看我的冒险之旅

    今天我们将学习并发编程三剑客之一的协程, 了解与进程线程的区别, 以及协程的创建

    在米斯特的解读下(因为日志使用的暗精灵语), 二人发现摩根的日志记载了异次元裂缝的存在,确认了使徒狄瑞吉是造成传染病的原因,并推断出诺伊佩拉的狄瑞吉只是狄瑞吉在传送过程中一部分能量泄露出来所产生的幻影。同时,摩根还在日志中记录了暴戾搜捕团的行踪,并且推断出爱丽丝有问题。而这些将会在不久的将来粉墨登场...

    协程

    协程也叫作纤程(Fiber),是一种在线程中,比线程更加轻量级的存在,由程序员自己写程序来管理. 我们可以将协程理解为运行在线程上的代码块, 协程挂起并不会引起线程阻塞, 他的作用是提高线程的利用率… 协程之间可以依靠邮箱来进行通信和数据共享, 了避免内存共享数据而带来的线程安全问题. 因为其轻量和高利用率的特点, 即使创建上千个线程也不会对系统造成很大负担, 而线程则恰恰相反. 协程是一种设计思想,不仅仅局限于某一门语言. 在Go, Java, Python 等语言中均有实现

    协程的优点

  • 由于自身带有上下文和栈,无需线程上下文切换的开销,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级;

  • 无需原子操作的锁定及同步的开销;

  • 方便切换控制流,简化编程模型

  • 单线程内就可以实现并发的效果,最大限度地利用cpu,且可扩展性高,成本低(注:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理)

  • 协程的缺点

  • 无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上

  • 协程与线程的比较

  • 在单线程同步模型中,任务按照顺序执行 如果某个任务因为I/O而阻塞,其他所有的任务都必须等待,直到它完成之后它们才能依次执行

  • 多线程模型中,多个任务分别在独立的线程中执行 这些线程由操作系统来管理,在多处理器系统上可以并行处理,或者在单处理器系统上交错执行. 这使得当某个线程阻塞在某个资源的同时其他线程得以继续执行

  • 协程版本的程序中,多个任务交错执行,但仍然在一个单独的线程控制中 当处理I/O或者其他昂贵的操作时,注册一个回调到事件循环中,然后当I/O操作完成时继续执行。回调描述了该如何处理某个事件. 事件循环轮询所有的事件,当事件到来时将它们分配给等待处理事件的回调函数。

  • asyncio实现协程(重点)

  • 正常的函数执行时是不会中断的,所以你要写一个能够中断的函数,就需要加 async

  • async 用来声明一个函数为异步函数,异步函数的特点是能在函数执行过程中挂起,去执行其他 异步函数,等到挂起条件(假设挂起条件是 sleep(5) )消失后,也就是5秒到了再回来执行

  • await 用来用来声明程序挂起,比如异步程序执行到某一步时需要等待的时间很长,就将此挂 起,去执行其他的异步程序。

  • asyncio 是python3.5之后的协程模块,是python实现并发重要的包,这个包使用事件循环驱动实 现并发

  • asyncio协程是写爬虫比较好的方式. 比多线程和多进程都好.开辟新的线程和进程是非常耗时的

  • 实操代码

    1. 不使用协程时

      # 不使用协程执行多个任务
      import time

      deffun1():
      for i in range(3):
      print(f'原子弹:第{i}次爆炸啦')
      time.sleep(1)
      return"fun1执行完毕"

      deffun2():
      for k in range(3):
      print(f'氢弹:第{k}次爆炸了')
      time.sleep(1)
      return"fun2执行完毕"

      defmain():
      fun1()
      fun2()

      if __name__ == "__main__":
      start_time = time.time()
      main()
      end_time = time.time()
      print(f"耗时{end_time - start_time}") # 不使用协程,耗时6秒


    2. 使用使用yield协程,实现任务切换

      # 不使用协程执行多个任务
      import time

      deffun1():
      for i in range(3):
      print(f'原子弹:第{i}次爆炸啦')
      yield# 只要方法包含了yield,就变成一个生成器
      time.sleep(1)
      return"fun1执行完毕"

      deffun2():
      g = fun1() # fun1是一个生成器,fun1()就不会直接调用,需要通过next()或for循环调用
      print(type(g))
      for k in range(3):
      print(f'氢弹:第{k}次爆炸了')
      next(g) # 继续执行fun1的代码
      time.sleep(1)
      return"fun2执行完毕"

      defmain():
      fun1()
      fun2()

      if __name__ == "__main__":
      start_time = time.time()
      main()
      end_time = time.time()
      print(f"耗时{end_time - start_time}") # 耗时5.0秒,效率差别不大


    3. 使用asyncio异步IO的典型使用方式实现协程

      实现步骤:

      实操代码

      # 不使用协程执行多个任务
      import asyncio
      import time

      asyncdeffun1():# async表示方法是异步的
      for i in range(3):
      print(f'原子弹:第{i}次爆炸啦')
      # await异步执行func1方法
      await asyncio.sleep(1)
      return"fun1执行完毕"

      asyncdeffun2():
      for k in range(3):
      print(f'氢弹:第{k}次爆炸了')
      # await异步执行func2方法
      await asyncio.sleep(1)
      return"fun2执行完毕"

      asyncdefmain():
      res = await asyncio.gather(fun1(), fun2())
      # 返回值为函数的返回值列表,本例为["func1执行完毕", "func2执行完毕"]
      print(res)
      if __name__ == "__main__":
      start_time = time.time()
      asyncio.run(main())
      end_time = time.time()
      print(f"耗时{end_time - start_time}") # 耗时3秒,效率极大提高


      实操结果 从这里可以看到, 这里进用了3.02s左右, 只比理论最短用时3s多了0.02s左右, 从这里可以看出使用协程的巨大优势

  • 创建两个异步方法fun1, fun2

  • 创建一个main方法来管理上面两个异步方法 await asyncio.gather(fun1(), fun2())

  • 主函数中通过 asyncio.run(main()) 来运行main方法

  • 今日冒险片段下

    根据日志的提示, 二人确实找到了挪移佩拉地区, 并遇到了一个像黑色野猪的怪物, 由于是使徒的一丝残影, 由于时间的流逝, 其攻击已经非常的弱了, 但是二人仍然花费一天时间将其击败. 但击败狄瑞吉的幻影也足以让了不起成功晋升至lv31.
    二人思考再三, 便将其交给摩根日记中的好友克伦特. 克伦特至此开始怀疑爱丽丝是整个事件的幕后指使,却苦于没有证据,只能罢手. 不过克伦特却凭借直觉发现盗尸者有与伪装者相近的气息,怀疑二者实为同宗,即伪装者与盗尸者本质都跟使徒脱不开关系,公国的圣职者曾经经历过与伪装者的暗黑圣战,于是克伦特托冒险家咨询歌兰蒂斯. 起初歌兰蒂斯并不相信类似伪装者的东西会再次出现,而当冒险家拿来「变异的心脏」时,歌兰蒂斯信了. 并且在此之后, 由此还引发了第二次暗黑圣战...
    同时在歌兰蒂斯那里了不起还了解到, 万年雪山附近一头冰龙暴动, 袭击了四剑圣之一的布万加所在的村庄, 而布万加也莫名失踪. 想到曾经帮助过自己的人遇到了危险, 了不起便决定出发, 去解救被困的朋友.

    戳蓝字 Python都知道 关注 我哦!

    PS Python都知道技术交流群(技术交流、摸鱼、白嫖课程为主)又不定时开放了,感兴趣的朋友,可以在下方公号内回复: 666 ,即可进入。

    老规矩 ,道友们还记得么, 右下角的 「在看」 点一下 如果感觉文章内容不错的话,记得分享朋友圈让更多的人知道!