Python的协程

协程的定义和原理:

  • 协程,又叫做纤程、微线程。协程的本质上是一个单线程程序,所以协程不能够使用计算机多核资源(但是可以利用多进程+协程实现利用多核资源)。通过记录应用层的上下文栈区,实现在运行中进行上下文跳转,达到可以选择性地运行想要运行的部分,以此提高程序的运行效率。

协程的作用:

  • 协程能够高效的完成并发任务,占用较少的资源,因此协程的并发量较高。

协程的优点:

  • 消耗资源少
  • 无需切换,节约系统开销
  • 无需同步互斥
  • IO并发性好

协程相对于多线程的优势:

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

协程的缺点:

  • 无法利用计算机多核(但是可以利用多进程+协程实现利用多核资源)

注意:

  • 协程是一个线程执行,针对上面提的协程的缺点,如果要利用计算机多核,可以使用多进程+协程,充分利用多核,发挥协程的高效率,又获得极高的性能。

Python中通常使用greenlet和gevent这两个模块实现协程。

greenlet

主要方法:

greenlet.greenlet()

  • 功能:生成协程对象

gr.switch()

  • 功能:选择要执行的协程事件

代码实现:

from greenlet import greenlet 

def test1():
    print("111")
    # 执行协程g2
    g2.switch()
    print("222")
    g2.switch()

def test2():
    print("333")
    g1.switch()
    print("444")

#协程对象
g1 = greenlet(test1)
g2 = greenlet(test2)

# 首先执行协程g1
g1.switch()

运行结果:

gk@gk-vm:~/python/test$ python3 greenlet_test.py 
111
333
222
444

观察运行结果可以发现两个协程内部发生了相互调用,达到了子程序的相互切换。

gevent

主要方法:

gevent.spawn(func,argv)

  • 功能:生成协程对象
  • 参数:func(协程函数),argv(给协程函数传参)
  • 返回值:返回协程对象

gevent.joinall()

  • 功能:回收协程
  • 参数:列表,将要回收的协程放入列表

gevent.sleep(n)

  • 功能:设置协程阻塞,让协程跳转
  • 参数:n(阻塞时间)

代码实现:

import gevent
from time import sleep

def test1():
    print("test1第一次运行")
    # 阻塞自动跳转执行test2
    gevent.sleep(1)
    print("test1第二次运行")

def test2():
    print("test2第一次运行")
    # 阻塞自动跳转执行test1
    gevent.sleep(2)
    print("test2第二次运行")

#生成协程
g1 = gevent.spawn(test1)
g2 = gevent.spawn(test2)

# 回收协程
gevent.joinall([g1,g2])

print("结束")

运行结果:

gk@gk-vm:~/python/test$ python3 gevent_test.py 
test1第一次运行
test2第一次运行
test1第二次运行
test2第二次运行
结束

另外:
上面的gevent.sleep()可以说只是一种模拟的阻塞,在实际的开发中,如果发生IO阻塞,gevent通常会自动切换。

代码实现(模拟请求5个网站,请求网站通常都会产生阻塞):

from gevent import monkey; monkey.patch_all()
import requests
import gevent

def get_url(url):
	print("开始请求url:", url)
	res = requests.get(url)
	print(url, "请求完毕")

g1 = gevent.spawn(get_url, "https://www.baidu.com/")
g2 = gevent.spawn(get_url, "https://www.csdn.net/")
g3 = gevent.spawn(get_url, "https://www.bilibili.com/")
g4 = gevent.spawn(get_url, "https://www.weibo.com/")
g5 = gevent.spawn(get_url, "https://www.dongqiudi.com/")

gevent.joinall([g1, g2, g3, g4, g5])

注意代码的第一行,由于gevent在切换IO操作(文件IO、网络IO)时是自动完成的,所以gevent需要通过修改Python自带的一些阻塞式系统调用的标准库,包括socket、ssl、threading和 select等模块,而变为协程,这一过程需要在启动时通过monkey patch完成。如果没有第一行代码,将无法实现协程的自动切换。

运行结果:

gk@gk-vm:~/python/test$ python3 gevent_test2.py 
开始请求url: https://www.baidu.com/
开始请求url: https://www.csdn.net/
开始请求url: https://www.bilibili.com/
开始请求url: https://www.weibo.com/
开始请求url: https://www.dongqiudi.com/
https://www.baidu.com/ 请求完毕
https://www.bilibili.com/ 请求完毕
https://www.dongqiudi.com/ 请求完毕
https://www.weibo.com/ 请求完毕
https://www.csdn.net/ 请求完毕

运行可以发现对于这种网络IO操作,使用协程效率真的非常高,多线程也适用于这种网络IO操作,但是协程却避免了多线程的线程间相互切换的消耗,协程比多线程具有更高的效率。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值