Python中的 协程

协程的定义

具体操作:https://blog.csdn.net/ZPeng_Yan/article/details/106456050
协程:

  1. 协程,又称微线程,纤程,英文名Coroutine。
  2. 协程由于由程序主动控制切换,没有线程切换的开销,所以执行效率极高。对于IO密集型任务非常适用,如果是cpu密集型,推荐多进程+协程的方式。
  3. 线程的切换会保存到CPU的栈里,协程拥有自己的寄存器上下文和栈,
  4. 协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈
  5. 协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态
  6. 协程最主要的作用是在单线程的条件下实现并发的效果,但实际上还是串行的(像yield一样)

协程的作用:是在执行函数A时,可以随时中断,去执行函数B,然后中断继续执行函数A(可以自由切换)。但这一过程并不是函数调用(没有调用语句),这一整个过程看似像多线程,然而协程只有一个线程执行.

说明

协程的主要特色是:
协程间是协同调度的,这使得并发量数万以上的时候,协程的性能是远远高于线程。
注意这里也是“并发”,不是“并行”。

常用库:greenlet gevent

协程优点:

1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级

2. 单线程内就可以实现并发的效果,最大限度地利用cpu

协程缺点:

1.协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程

2.协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程

python中的协程

一个协程是一个函数/子程序(可以认为函数和子程序是指一个东西)。这个函数可以暂停执行, 把执行权让给 YieldInstruction,等 YieldInstruction 执行完成后,这个函数可以继续执行。 这个函数可以多次这样的暂停与继续。

注:这里的 YieldInstruction, 我们其实也可以简单理解为函数。

协程可以在“卡住”的时候可以干其它事情。

async def long_task():
...     print('long task started')
...     await asyncio.sleep(1)
...     print('long task finished')
...
>>> loop.create_task(long_task())
<Task pending coro=<long_task() running at <stdin>:1>>
>>> loop.create_task(job1())                                                                                                                                                                                                                            >>>>> loop.create_task(job1())
<Task pending coro=<job1() running at <stdin>:1>>
>>>
>>> try:
...     loop.run_forever()
... except KeyboardInterrupt:
...     pass
...
long task started
job1 started...
job1 paused
hello world
job1 resumed
job1 finished
long task finished

从这段程序的输出可以看出,程序本来是在执行 long task 协程,但由于 long task 要 await sleep 1 秒,于是 long task 自动暂停了,hello_world 协程自动开始执行, hello world 执行完之后,long task 继续执行。

协程有两种定义的方法, 其中使用生成器形式定义的协程叫做 generator-based coroutine, 通过 async/await 声明的协程叫做 native coroutine,两者底层实现都是生成器。接着, 我们阐述了协程的概念,从概念和例子出发,讲了协程和生成器最主要的特征:可以暂停执行和恢复执行。

协程异常处理
使用协程的时候一定加了很多的异常,但百密而一疏,总是会有想象不到的异常发生,这个时候为了不让程序整体崩溃应该使用协程的额外异常处理方法,这个方法会去执行绑定的回调函数.

g_dict=dict{}
g = gevent.spawn(self._g_fetcher, feed_name)   # 创建协程
g_dict[feed_name] = [g,False]
g.link_exception(self._link_exception_callback)  # 给该协程添加出现处理不了的异常时候的回调函数
def _link_exception_callback(self, g):
    # 可能遇到无法修复的错误,需要修改代码 todo 报警
    # 可以在这个函数里面做一些错误异常的打印,或者文件的关闭,连接的关闭.
    self.terminated_flag = True # 停止整个程序 让 supervior重启
    logger.info("_link_exception_callback {0} {1}".format(g, g.exception))
    self._kill_sleep_gevent()   # 轮询结束休眠的协程

def _kill_sleep_gevent(self):
    for i,is_sleep in g_dict.items():
        if is_sleep[1] == "True":
            gevent.kill(is_sleep[0])

Gevent 使用协程

greenlet框架实现协程(封装yield的基础库)
greenlet机制的主要思想是:生成器函数或者协程函数中的yield语句挂起函数的执行,直到稍后使用next()或send()操作进行恢复为止。可以使用一个调度器循环在一组生成器函数之间协作多个任务。greentlet是python中实现我们所谓的"Coroutine(协程)"的一个基础库。

基于greenlet框架的高级库gevent模块
gevent是第三方库,通过greenlet实现协程,其基本思想是:

当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。

注:Gevent只用起一个线程,当请求发出去后gevent就不管,永远就只有一个线程工作,谁先回来先处理
import gevent
import requests
from gevent import monkey

monkey.patch_all()

# 这些请求谁先回来就先处理谁
def fetch_async(method, url, req_kwargs):
    response = requests.request(method=method, url=url, **req_kwargs)
    print(response.url, response.content)

# ##### 发送请求 #####
gevent.joinall([
    gevent.spawn(fetch_async, method='get', url='https://www.python.org/', req_kwargs={}),
    gevent.spawn(fetch_async, method='get', url='https://www.google.com/', req_kwargs={}),
    gevent.spawn(fetch_async, method='get', url='https://github.com/', req_kwargs={}),
])

原生协程

协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存,在调度回来的时候,恢复先前保存的寄存器上下文和栈。因此协程能保留上一次调用时的状态,即所有局部状态的一个特定组合

async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖

import time
def job(t):
    time.sleep(t) 
    print('用了%s' % t)
def main():
    [job(t) for t in range(1,3)]
start = time.time()
main()
print(time.time()-start)

import time
import asyncio
async def job(t):  # 使用 async 关键字将一个函数定义为协程
    await asyncio.sleep(t)  # 等待 t 秒, 期间切换执行其他任务
    print('用了%s秒' % t)
async def main(loop):  # 使用 async 关键字将一个函数定义为协程
    tasks = [loop.create_task(job(t)) for t in range(1,3)]  # 创建任务, 不立即执行
    await asyncio.wait(tasks)  # 执行并等待所有任务完成
start = time.time()
loop = asyncio.get_event_loop()  # 建立 loop
loop.run_until_complete(main(loop))  # 执行 loop
loop.close()  # 关闭 loop

print(time.time()-start)

你可能感兴趣的:(Python中的 协程)