python10-协程

本文详细介绍了线程、协程和进程的区别,强调了协程在规避IO操作上的优势。通过gevent和asyncio两个模块展示了Python中如何实现协程,并通过实例演示了它们在处理并发和IO操作中的应用。同时,文章提到了gevent的monkey_patch用于避免特定IO操作的阻塞,并讨论了协程在高并发场景下的效率提升。此外,还介绍了asyncio模块中的await关键字和底层原理。
摘要由CSDN通过智能技术生成

1,概念

  • 线程:

    • 正常的开发语言:多线程可以利用多核。
    • cpython解释器下的多个线程不能利用多核:这本质上是规避了所有IO操作的单线程。
  • 协程:

    • 是操作系统不可见的。
    • 协程本质就是一条线程,多个任务==在一条线程上来回切换==(因)以规避IO操作(果),以达到将一条线程中的IO操作降到最低的目的。
  • 进程、线程、协程之间的对比:

    操作数据隔离/共享数据是否安全操作级别开销多核
    进程数据隔离数据不安全操作系统级别(dis.dis)非常大能利用多核
    线程数据共享数据不安全操作系统级别开销小(几百倍)不能利用多核
    协程数据共享(一条线程,肯定共享)数据安全用户级别更小(函数切换级别)不能利用多核
  • 协程相比于线程的缺陷:

    • 协程的所有切换都基于用户,只有在用户级别能够感知到的IO操作才会用协程模块做切换以进行规避,比如:socket、请求网页等。
    • 但是一些和文件操作相关的IO只有操作系统能够感知到,此时只能用线程。
    • 线程的感知更加细腻。
  • 用户级别操作的协程有什么好处:

    • 减轻了操作系统的负担。
    • 一条线程如果开了多个协程,能够多争取一些时间片来被CPU执行,提高程序的效率。

2,模块

  • 切换并规避IO的两个模块:
    • gevent:利用了 greenlet 底层模块完成的切换+自动规避IO的功能;
    • asyncio:利用了 yield 底层语法完成的切换+自动规避IO的功能。
  • 基于python原生的协程概念的发展史:
    • tornado:一种异步的web框架,基于yield实现。
    • yield from,更好的实现协程。
    • send,更好的实现协程。
    • asyncio模块,基于python原生的协程的概念正式被成立。
  • 特殊的在python中提供协程功能的关键字:aysnc,await。

3,gevent模块

  • 带有IO操作的命令**(conn.recv,使用gevent,务必将import gevent,from gevent import monkey,monkey.patch_all()三行语句放在其他所有的import语句之前)**写在函数func中,然后提交func给gevent;如果主程序里没有阻塞,需要自己加上阻塞

    '''例子1'''
    import gevent
    
    def func():
        print('start func')
        gevent.sleep(1)
        print('end func')
    
    g1 = gevent.spawn(func)
    g2 = gevent.spawn(func)
    g3 = gevent.spawn(func)
    gevent.joinall([g1, g2, g3])
    # g1.join()       #必须阻塞,因为协程只有在有IO操作的时候才切换
    # g2.join()
    # g3.join()
    
  • gevent.sleep与time.sleep不同;如果要想让time.sleep和gevent.sleep起到一样的效果,需要如下操作:

    '''例子2'''
    import gevent
    from gevent import monkey
    monkey.patch_all(thread=False, select=False)
    import time
    
    def func():
        print('start func')
        time.sleep(1) #与time.sleep不同,如果想要让
        print('end func')
    
    g1 = gevent.spawn(func)
    g2 = gevent.spawn(func)
    g3 = gevent.spawn(func)
    gevent.joinall([g1, g2, g3])
    
  • 使用协程进行一个服务端与多个客户端的通信

    '''===============================server==============================='''
    import gevent
    from gevent import monkey
    monkey.patch_all()
    import socket
    
    def func(conn):
        while True:
            msg = conn.recv(1024).decode('utf-8')
            MSG = msg.upper()
            conn.send(MSG.encode('utf-8'))
    
    sk = socket.socket()
    sk.bind(('127.0.0.1', 9000))
    sk.listen()
    
    while True:
        conn, _ = sk.accept()
        gevent.spawn(func, conn)
    
    '''===============================client==============================='''
    import socket
    import time
    
    sk = socket.socket()
    sk.connect(('127.0.0.1', 9000))
    
    while True:
        sk.send(b'hello')
        msg = sk.recv(1024)
        print(msg)
        time.sleep(0.5)
    
  • 协程的效率,起500个客户端

    4核CPU
    总的进程数5个
    每个进程的线程数20个
    每个线程的协程数500个

    一个四核机器开5个进程,每个进程开20个线程,每个线程开500个协程,极限可以抗50000的并发。(一般每个机器抗30000的并发)

    '''===============================client==============================='''
    import socket
    import time
    from threading import Thread
    
    def client():
        sk = socket.socket()
        sk.connect(('127.0.0.1', 9000))
    
        while True:
            sk.send(b'hello')
            msg = sk.recv(1024)
            print(msg)
            time.sleep(0.5)
    
    for i in range(500):
        Thread(target=client).start()
    
  • 测试gevent是否能可以对引起阻塞的操作起到协程的作用,可以进行如下判断:

    import socket
    print(socket.socket)
    import gevent
    from gevent import monkey
    monkey.patch_all()
    import socket
    print(socket.socket)
    
    '''两次打印的结果相同,则gevent可以规避该IO操作;如果不同,gevent无法规避该IO操作;看patch_all源码,所有为True的都支持'''
    def patch_all(socket=True, dns=True, time=True, select=True, thread=True, os=True, ssl=True,
                  subprocess=True, sys=False, aggressive=True, Event=True,
                  builtins=True, signal=True,
                  queue=True, contextvars=True,
                  **kwargs):
    

4,asyncio模块

  • await
    • 用在可能会发生阻塞的方法前(await后面必须跟一个IO操作)
    • 必须写在一个async函数里
  • 底层比较复杂,https://www.cnblogs.com/Eva-J/p/10437164.html
import asyncio

async def func(name):
    print('start', name)
    await asyncio.sleep(1)  #await标志会在此处切走
    print('end')

loop = asyncio.get_event_loop()
# loop.run_until_complete(func('a'))
loop.run_until_complete(asyncio.wait([func('a'), func('b')]))
  • 原理

    • yield和next配合可以做到在执行函数的过程中从函数中切出去。

    • 使用python实现的协程:

      import time
      
      def sleep(n):
          print('start sleep')
          yield time.time()+n
          print('end sleep')
      
      def func(n):
          print(123)
          g = sleep(n)
          yield from g    #await就相当于yield from
          print(456)
      
      g1 = func(2)
      g2 = func(1)
      ret1 = next(g1)
      ret2 = next(g2)
      timeDict = {ret1: g1, ret2: g2} #仅多1s就已经体现出了异步
      print(timeDict)
      while timeDict:
          minTime = min(timeDict)
          time.sleep(minTime - time.time())   #异步,同时睡了1s
          try:
              next(timeDict[minTime])
          except StopIteration:
              pass
          del timeDict[minTime]
      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tensor_zhang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值