马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
什么是协程?
协程(Coroutine)是一种用于并发编程的技术,它允许在单个线程内实现多任务调度。协程可以在执行过程中暂停,并在需要时恢复执行,这使得它们非常适合处置处罚I/O密集型任务,如网络哀求、文件读写等。
- 非抢占式调度:协程由程序显式地控制切换点,不像线程那样由操作系统调度。这意味着你可以精确控制何时暂停和恢复一个协程。
- 轻量级:由于不需要创建新的线程或历程,协程的开销更小。
- 单线程内并发:所有的任务都运行在一个单独的主线程中,因此制止了多线程编程中的一些复杂性,如竞争条件和死锁。
协程的实现
在 Python 中,协程的实现有多种,此中主要依赖于生成器和 asyncio 库。关于asyncio的详细用法本章仅做实现示例,下章再详细拆解。
greenlet实现
- from greenlet import greenlet
- def func1():
- print(1)
- gr2.switch()
- print(2)
- gr2.switch()
- def func2():
- print(3)
- gr1.switch()
- print(4)
- gr1 = greenlet(func1)
- gr2 = greenlet(func2)
- gr1.switch()
复制代码 结果:
- 定义两个函数 func1() 和 func2(),每个函数内部都有多个步骤,并且在某些步骤之后会调用对方的 switch 方法来进行上下文切换。
- 利用 greenlet() 函数分别将这两个函数包装成 Greenlets:gr1 和 gr2.
- 最后,通过调用 gr1.switch() 开始执行第一个任务,这会导致程序在差异任务之间来回切换。
生成器实现
什么是yield关键字?
yield 关键字用于定义一个生成器(generator)函数。生成器是一种特别的迭代器,它允许你逐个产生值,而不是一次性创建并返回一个包含所有值的列表。利用 yield 的函数在执行过程中可以多次 yield 值,每次 yield 都会返回一个值,并在下一次从该点继续执行。
yield 是 Python 中实现协程和异步编程的基础,特别是在 Python 3.3 之前,当时的 asyncio 库依赖于生成器来实现协程。在 Python 3.5 及以后版本中,引入了 async 和 await 语法,提供了更高级的异步编程本领,但 yield 仍然是 Python 编程中一个非常有用的关键字。
- 惰性盘算:生成器在调用时不会立即盘算所有值,而是在每次迭代时盘算并产生下一个值。
- 状态保存:当生成器函数 yield 一个值时,它的所有当地变量和当前的执行状态都会被保存,以便下次从该点继续执行。
- 逐个产生:生成器一次产生一个值,这使得它们在处置处罚大量数据时非常高效,因为不需要一次性将所有数据加载到内存中。
- 可迭代对象:生成器本身是可迭代对象,可以被 for 循环或 next() 函数迭代。
- def count_up_to(num):
- """
- `count_up_to` 函数是一个生成器,它在每次迭代时 `yield` 当前的 `count` 值,并在下一次迭代时从上次停下的地方继续执行。
- 不使用yield的话,使用这段代码可以达到同样的效果:
- result = list()
- count = 1
- while count <= num:
- result.append(count)
- count += 1
- return result
- """
- count = 1
- while count <= num:
- yield count
- count += 1
- # 使用生成器
- for number in count_up_to(5):
- print(number)
- print("分割线".center(30, "="))
- def chain_generators(*gens):
- """
- yield from 允许你将一个生成器的输出委托给另一个生成器,简化了生成器的嵌套调用。
- 如果不使用yield from来简化:
- for gen in gens:
- # 迭代每个生成器对象
- for value in gen:
- # 逐个 yield 生成器的值
- yield value
- """
- for gen in gens:
- yield from gen
- for number in chain_generators(count_up_to(2), count_up_to(3)):
- print(number)
复制代码 结果输出:
- 1
- 2
- 3
- 4
- 5
- =============分割线==============
- 1
- 2
- 1
- 2
- 3
复制代码 实现协程示例
- def func1():
- yield 1
- yield from func2()
- yield 2
- def func2():
- yield 3
- yield 4
- f1 = func1()
- for item in f1:
- print(item)
复制代码 结果
- 首先,f1 开始执行,yield 1 被执行,生成器产生值 1。
- 然后,yield from func2() 被执行。func2 产生第一个值 3,f1 将其通报给迭代器,以是 3 被打印出来。
- func2 继续产生第二个值 4,f1 同样将其通报给迭代器,以是 4 也被打印出来。
- func2 没有更多的 yield 语句,以是 yield from 表达式完成,控制权回到 f1。
- f1 继续执行,yield 2 被执行,生成器产生值 2。
asyncio实现
旧版本实现
asyncio 是 Python 的一个标准库,用于编写单线程并发代码。
在python3.4及之后,asyncio 库可以利用 @asyncio.coroutine 装饰器来定义协程。这个装饰器是编写协程的早期方法,它允许你在函数定义之前添加 @asyncio.coroutine,以便利用 yield from 来暂停和恢复协程的执行。
- import asyncio
- # 在3.8版本后这种装饰器的写法就废弃了 ,让你用async def来定义函数替代。运行会提示警告:
- # DeprecationWarning: "@coroutine" decorator is deprecated since Python 3.8, use "async def" instead
- @asyncio.coroutine
- def func1():
- print(1)
- # 模拟网络IO请求
- yield from asyncio.sleep(2) # 遇到IO耗时操作,自动化切换到tasks中的其他任务
- print(2)
- @asyncio.coroutine
- def func2():
- print(3)
- # 模拟网络IO请求
- yield from asyncio.sleep(2) # 遇到IO耗时操作,自动化切换到tasks中的其他任务
- print(4)
- tasks = [
- asyncio.ensure_future(func1()),
- asyncio.ensure_future(func2())
- ]
- loop = asyncio.get_event_loop()
- loop.run_until_complete(asyncio.wait(tasks))
复制代码 输出:
- 首先,它将运行 func1,直到遇到 asyncio.sleep(2)。
- 由于 asyncio.sleep(2) 是一个异步等待调用,事件循环将挂起 func1 并查抄是否有其他任务可以运行。
- 然后,事件循环切换到 func2 并执行它,直到遇到 asyncio.sleep(2),同样被挂起。
- 事件循环再次查抄任务队列,发现没有其他任务可以运行,将等待一段时间(由 asyncio.sleep 的参数决定)。
- 2 秒后,func1 和 func2 中的 asyncio.sleep 都完成,事件循环将恢复它们的执行。
新版本实现
在3.7版本后,保举利用 async def 定义协程,并用 await 来等待异步操作。
- import asyncio
- async def func1():
- """
- 相比上面的使用装饰器,推荐新的写法:
- 将装饰器@asyncio.coroutine去掉,def方法加上async前缀
- yield from 改为使用await
- """
- print(1)
- # 模拟网络IO请求
- await asyncio.sleep(2) # 遇到IO耗时操作,自动化切换到tasks中的其他任务
- print(2)
- async def func2():
- print(3)
- # 模拟网络IO请求
- await asyncio.sleep(2) # 遇到IO耗时操作,自动化切换到tasks中的其他任务
- print(4)
- tasks = [
- asyncio.ensure_future(func1()),
- asyncio.ensure_future(func2())
- ]
- loop = asyncio.get_event_loop()
- loop.run_until_complete(asyncio.wait(tasks))
复制代码 协程与多线程、多历程的区别接洽
协程(Coroutines)
协程是一种程序组件,能够在等待操作完成时挂起执行,而无需壅闭整个线程。它们通常用于 I/O 密集型任务,因为 I/O 操作(如网络哀求、文件读写)通常涉及等待。协程的优点包括:
- 轻量级:协程的创建和切换开销远小于线程。
- 非抢占式:协程的执行次序由程序逻辑控制,而不是由操作系统强制抢占。
- 易于编写:利用 async/await 语法可以使异步代码的逻辑更清晰。
Python 中的 asyncio 库提供了对协程的支持。
多线程(Multithreading)
多线程允许在同一时间内在同一个历程中并行运行多个线程。线程共享历程的内存和资源,这使得线程间通信和数据共享变得容易,但也引入了同步和竞态条件的问题。多线程的优点包括:
- 资源共享:线程可以共享历程的内存和文件形貌符等资源。
- 简化编程:线程的创建和管理相对简单。
- 适用性:适合盘算密集型和 I/O 密集型任务。
Python 的 threading 模块提供了多线程的支持。
多历程(Multiprocessing)
多历程是指在操作系统级别上同时运行多个历程。每个历程有自己的内存空间,这意味着历程间通信需要通过明确的 IPC(历程间通信)机制。多历程的优点包括:
- 隔离性:历程间相互独立,一个历程的瓦解不会直接影响到其他历程。
- 资源分配:操作系统可以有效地管理差异历程的资源分配。
- CPU 密集型任务:由于每个历程有自己的内存空间,多历程适合处置处罚 CPU 密集型任务。
Python 的 multiprocessing 模块提供了多历程的支持。
区别与接洽
- 资源占用:协程是单线程内的并发,资源占用最少;多线程共享历程资源,资源占用适中;多历程有最大的资源占用。
- 上下文切换开销:协程上下文切换的开销最小;多线程次之;多历程的上下文切换开销最大。
- 编程复杂度:协程编程模型最简单;多线程需要处置处罚线程安全和同步问题;多历程由于历程隔断离,编程复杂度介于协程和多线程之间。
- 适用场景:协程适合 I/O 密集型任务;多线程适合盘算和 I/O 混合型任务;多历程适合 CPU 密集型任务和需要高隔离性的场景。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |