基本概念

  • 协程,又称微线程,纤程,英文名Coroutine
  • 协程是python中另外一种实现多任务的方式,只不过比线程更小占用更小的执行单元。为什么说它是一个执行单元,因为它自带CPU上下文,这样可以在合适的时机把一个协程切换到另一个协程
  • 通俗的理解:在一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数中执行。注意不是通过调用函数的方式做到的,并且切换的次数以及什么时候再切换到原来的函数都由开发者自己确定
  • 协程和线程的差异:在实现多任务时,线程切换从系统层面远不止保存和恢复CPU上下文这么简单。操作系统为了程序运行的高效性,每个线程都有自己的缓存Cache等数据,操作系统还会帮你做这些数据的恢复操作。所以线程的切换非常耗性能,但是协程的切换只是单纯的操作CPU的上下文

    协程的实现方式

    使用greenlet

    ```python from greenlet import greenlet import time

def work1(): while True: print(“—-A—“) gr2.switch() time.sleep(0.5)

def work2(): while True: print(“—-B—“) gr1.switch() time.sleep(0.5)

gr1 = greenlet(work1) gr2 = greenlet(work2)

gr1.switch() # 切换到gr1中运行

  1. <a name="z1xaJ"></a>
  2. ### 使用gevent
  3. - greenlet已经实现了协程,但是还需要人工切换,有点麻烦,python还有一个比greenlet更强大的并且能够自动切换任务的模块gevent
  4. - 其原理是当一个greenlet遇到IO操作时,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行
  5. - 由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有协程在运行,而不是等待IO操作
  6. - [gevent的常用方法](https://blog.csdn.net/biheyu828/article/details/86593413)
  7. - gevent.spawn():创建一个普通的greenlet对象并切换
  8. - gevent.spawn_later(seconds=3):延时创建一个普通的greenlet并切换
  9. - gevent.spawn_raw():创建的协程对象属于一个组
  10. - gevent.getcurrent():返回当前正在执行的greenlet
  11. - gevent.joinall(jobs):将协程任务添加到事件循环,接收一个任务列表
  12. - gevent.wait():可以替代join函数等待循环结束,也可以传入协程对象列表
  13. - gevent.kill():杀死一个协程
  14. - gevent.killall():杀死一个协程列表里的所有协程
  15. - monkey.patch_all():会自动将python的一些标准模块替换成gevent模块
  16. - 只需要在代码最开头添加:gevent.monkey.patch_all();
  17. - 这样我们在后面使用socket的时候能够跟之前一样使用,可是它变成非阻塞的了
  18. ```python
  19. import gevent
  20. def f(n):
  21. for i in range(n):
  22. print(gevent.getcurrent(), i)
  23. g1 = gevent.spawn(f, 3)
  24. g2 = gevent.spawn(f, 3)
  25. g3 = gevent.spawn(f, 3)
  26. g1.join()
  27. g2.join()
  28. g3.join()
  29. '''
  30. 运行结果:
  31. <Greenlet at 0x2778a697ae0: f(3)> 0
  32. <Greenlet at 0x2778a697ae0: f(3)> 1
  33. <Greenlet at 0x2778a697ae0: f(3)> 2
  34. <Greenlet at 0x2778a697d00: f(3)> 0
  35. <Greenlet at 0x2778a697d00: f(3)> 1
  36. <Greenlet at 0x2778a697d00: f(3)> 2
  37. <Greenlet at 0x2778a697bf0: f(3)> 0
  38. <Greenlet at 0x2778a697bf0: f(3)> 1
  39. <Greenlet at 0x2778a697bf0: f(3)> 2
  40. '''
import gevent

def f(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        gevent.sleep(1)  # 用来模拟一个耗时操作

g1 = gevent.spawn(f, 3)
g2 = gevent.spawn(f, 3)
g3 = gevent.spawn(f, 3)
g1.join()
g2.join()
g3.join()

'''
运行结果:
<Greenlet at 0x16d79be7ae0: f(3)> 0
<Greenlet at 0x16d79be7d00: f(3)> 0
<Greenlet at 0x16d79be7bf0: f(3)> 0
<Greenlet at 0x16d79be7ae0: f(3)> 1
<Greenlet at 0x16d79be7d00: f(3)> 1
<Greenlet at 0x16d79be7bf0: f(3)> 1
<Greenlet at 0x16d79be7ae0: f(3)> 2
<Greenlet at 0x16d79be7d00: f(3)> 2
<Greenlet at 0x16d79be7bf0: f(3)> 2
'''
from gevent import monkey
import gevent
import random
import time

monkey.patch_all()  # 放在代码最前面

def coroutine_work(coroutine_name):
    for i in range(3):
        print(coroutine_name, i)
        time.sleep(random.random())  # 模拟I/O阻塞时间不确定

gevent.joinall([
    gevent.spawn(coroutine_work, "work1"),
    gevent.spawn(coroutine_work, "work2")
])

进程、线程、协程对比

  • 进程是资源分配的单位
  • 线程是操作系统内核调度的基本单位
  • 进程切换需要的资源最大,效率最低
  • 线程切换需要的资源一般,效率一般
  • 协程切换需要的资源很小,效率高