欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

Python 并发性的四种实现方式

最编程 2024-06-09 09:41:52
...

协程


协程不是计算机提供的,是人为创造的上下文切换技术,也可以被称为微线程。简而言之 其实就是在一个线程中实现代码块相互切换执行
我们知道正常代码是从上到下依次执行,一个方法或函数操作完毕后才会进入下一个方法或函数执行。例如:

def func1():
	print(1)
	print(2)
	
def func2():
	print(3)
	print(4)

func1()
func2()

此时代码执行逻辑一定是先执行完func1()对象里的语句再执行func2() ,这种称为同步。但是如果我们想在func1()对象中print(1)后切换到func2()该怎么做呢?


可以采用以下几种基于协程的方式:

  • greenlet。
  • yield 关键字
  • asyncio 装饰器(py3.4之后引入)
  • async、await关键字(py3.5之后引入)【推荐】

1. greenlet实现协程


# greenlet是第三方模块需先引入
pip3 install greenlet
# -*- coding: utf-8 -*-
# author: micher.yu
# Time:2022/01/08
# simple_desc :
from greenlet import greenlet


def func1():
	print(1)  # 第二步:输出1
	gr2.switch()  # 第三步:切换到 func2 函数
	print(2)  # 第六步:输出2
	gr2.switch()  # 第七步:切换到func2 函数(如果不切换的话句柄会继续往下执行,也就不会进入func2 输出4)


def func2():
	print(3)  # 第四步:输出3
	gr1.switch()  # 第五步:切换到func1 函数
	print(4)  # 第八步:输出4,func2函数 执行完毕句柄继续往下执行


def func3():
	print(5)  # 第十步:输出5


gr1 = greenlet(func1)  # 此处只是生成greenlet包装的func1对象,代码并不会实际运行
gr2 = greenlet(func2)  # 此处生成greenlet包装的func2对象

gr1.switch()  # 第一步:此处是正式执行func1()对象
func3()  # 第九步:实例化func3

# 所以实际输出会是 1 3 2 4 5

2. yield关键字


不推荐,实际应用场景比较少。

如果对yield关键字还不太熟悉的话可以参考往期这篇文章详解python三大器——迭代器、生成器、装饰器其中生成器部分有详细讲解

def func1():
	yield 1
	yield from func2()  # 这里其实相当于for item in func2(): yield item 
	yield 2


def func2():
	yield 3
	yield 4


for item in func1():
	print(item)

# 输出结果将会是:1 3 4 2

3. asyncio 模块


在python3.4及之后的版本才可使用,这个框架使用事件循环来编排回调和异步任务。事件循环位于事件循环策略的上下文中。
下图是协程,事件循环和策略之间的相互作用
在这里插入图片描述
注意:asyncio牛逼在于遇到IO阻塞自动切换!

下面我们使用@asyncio.coroutine装饰器(py3.10+会移除)定义了两个协程函数。(基于生成器的协程

import asyncio


@asyncio.coroutine
def func1():
	print(1)
	# 此处用asyncio.sleep(2)来模拟IO耗时(asyncio.sleep也是一个协程对象,不能用time.sleep()),asyncio定义的协程函数遇到IO操作时会自动切换到事件循环中的其他任务
	yield from asyncio.sleep(2)  
	print(2)


@asyncio.coroutine
def func2():
	print(3)
	yield from asyncio.sleep(2)
	print(4)

PS:如果py版本高于3.8依然可以使用asyncio.coroutine装饰器但是会有告警建议你使用async & await关键字来定义协程函数,不会影响使用!
在这里插入图片描述
协程函数并不能像普通函数一样直接实例化运行,调用协程函数协程并不会开始运行,只是返回一个协程对象。

fun1() # 此处是不会有结果的

可以通过 asyncio.iscoroutine 来验证是否是协程对象

print(asyncio.iscoroutine(func1()))  # True

协程对象必须在事件循环中运行,我们可以通过asyncio.get_event_loop方法来获取当前正在运行的循环实例。如loop对象,然后把协程对象交给 loop.run_until_complete,协程对象随后会在loop 里得到运行。

loop = asyncio.get_event_loop()
loop.run_until_complete(func1())
# 运行结果为:
# 1
# 等待2s
# 2

run_until_complete 是一个阻塞(blocking)调用,直到协程运行结束,它才返回;所以他必须接受的是一个可等待对象协程, 任务future对象)。

可等待对象:

  • 协程对象:协程函数实例化后就是协程对象

  • future对象:asyncio.futures.Future对象用来链接 底层回调式代码 和高层异步/等待式代码,可以简单理解为future对象是可以使程序hang在某个地方等待有结果了之后再继续执行。官方文档

    1. 创建future对象:loop.create_future()
      import asyncio
      
      async def main():
      	# 获取当前事件循环
      	loop = asyncio.get_running_loop()
      	# 单单只是创建一个future对象
      	fut = loop.create_future()
      	# future对象因为什么都没做也就没返回值,所以await会一直等待下去程序就会hang住
      	await fut
      
      
      asyncio.run(main())
      print(1)
      
      在这里插入图片描述
    2. future对象.set_result()方法
      async def func(fut):
      	fut

推荐阅读