【百尺竿头,更进一步学Python】Python进阶课程——Python协程

【百尺竿头,更进一步学Python】Python进阶课程——Python协程

我们都知道从编程层次而言,多任务的实现可以通过:多进程、多线程、协程来实现.

多进程和多线程在前几篇博客中我们已经进行了具体的讲解,今天我们就来讲最后一个协程(协程不是携程奥!).

协程(Coroutine)

什么是协程?

  • 协程,又称微线程,纤程。

  • 协程是用户级别的轻量级线程。

  • 协程主解决的是IO的操作。

  • 协程就是协助程序,以前我们学过的线程和进程都是抢占式特点,线程和进程的切换我们是不能参与的。

  • 而协程是非抢占式特点,协程也存在着切换,这种切换是由我们用户来控制的。

  • 协程就是在子程序执行的过程中,转而执行别的子程序,然后在返回来接着执行,这个过程并不是函数调用,而是中断。

  • 协程拥有自己的寄存器上下文和栈。协程调度时,将寄存器上下文和栈保存到其他的地方,之后切回来时,恢复之前保存的寄存器上下文和栈继续执行。

协程的优点

  1. 协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
  2. 不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

那么问题来了!协程是一个线程执行,那怎么利用多核CPU呢?

  • 最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

为什么要使用协程

我们都知道Python中,我们已经有了线程和进程,为什么还要使用协程呢?

  1. 上面已经提到,协程是程序员写调度逻辑的,不需要CPU进行线程的切换,因此也就省下了切换上下文的开销,提升了性能。

  2. 同时协程不需要使用多线程中的锁机制,只有一个线程,因此在协程中控制共享资源时不需加锁,所以执行效率也比多线程高。

或许你也注意到,协程其实就是一个单线程,那么自然它也无法利用多核资源。因此在现实中,我们通常是使用多进程+协程的方法,这样既充分的利用多核的优势,又充分发挥协程的高效率,从而获得高性能。

如何使用协程?

协程的实现由很多种,在此我们介绍基本的三种方法。

(1)使用yield实现协程

  • 我们使用生产者/消费者的例子讲解协程的使用。

def consumer(name):
	print("consumer %s 要开始吃东西了"%name)
	while True:
		bone=yield
		print("%s正在吃东西 %s"%(name,bone))
	
def producer(obj1,obj2):
	obj1.send(None)   #和obj1.__next__()等价
	obj2.send(None)    
	n=0
	while n<5:
		n+=1
		print("producer 正在生产食品 %s"%n)
		obj1.send(n)
		obj2.send(n)
		
if __name__=='__main__':
	con1=consumer("张三")
	con2=consumer("李四")
	producer(con1,con2)    
1234567891011121314151617181920

不明白yield的运行机制的可以看看我之前的文章。在线程博客中有详细的讲解.

(2)使用greenlet实现协程

  • 为了更好的使用协程来完成多任务,Python中的greenlet模块主要就是对其进行封装,从而使得切换任务变得更加简单,使用greenlet模块可以进行手动切换。

实例:

from greenlet import greenlet
import time

def upload():
    while True:
        time.sleep(0.5)
        print("上传文件")
        grt2.switch()

def download():
    while True:
        time.sleep(1)
        print("下载文件")
        grt1.switch()

grt1=greenlet(upload)
grt2=greenlet(download)

# 控制download先执行
grt2.switch()

(3)使用gevent实现协程

  • 使用greenlet模块时我们需要进行人工切换,而gevent会自动识别程序内部的IO操作,当子程序遇到IO时,它会自动切换到子程序。若所有的子程序进入到IO,则阻塞。
  • 由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行而不是等待IO

安装gevent包在Linux终端命令窗口使用下面命令进行安装:

sudo apt-get install python3-gevent

实例:

import gevent
import time

def upload(num):
    for i in range(num):
        gevent.sleep(1)
        print(f"{gevent.getcurrent()}------{i}")

def download(num):
    for i in range(num):
        gevent.sleep(1)
        print(f"{gevent.getcurrent()}------{i}")

gvt1 = gevent.spawn(upload,5)
gvt2 = gevent.spawn(download,5)
print(time.time())
gvt1.join()
gvt2.join()
print(time.time())

用来模拟一个耗时的操作,time模块中的sleep换成gevent模块中的sleep

给程序打补丁

这个补丁就是理解为是进行替换工作

#1.导入monkey模块
from gevent import monkey
#2.然后在后面调用patch_all方法
monkey.patch_all()
#3.剩下的工作完全按照之前的开发流程

线程和协程的差异

  • 在实现多任务的时候,线程切换是从系统中远不止保存和恢复CPU上下文这么简单,操作系统为了程序运行的高效性每个线程都有自己缓存Cache等等数据,操作系统还会帮我们做这些数据的回复操作。所以说多线程的切换非常消耗资源及计算机性能。
  • 但是协程的切换只是非常单纯的操作CPU的上下文,只是切换CPU去不同的任务中从刚才停止的代码开始执行。所以协程一秒钟切换个几百万次也不是问题

猜你喜欢

转载自blog.csdn.net/XVJINHUA954/article/details/108523420