目录

  • 十二、Python中协程
  • 12.1 协程的定义
  • 12.2 协程
  • 12.3 协程池
  • 12.4 总结


十二、Python中协程

12.1 协程的定义

协程(Coroutine):是一种比线程更加轻量级的存在,也称微线程,协程可以理解为一个特殊的函数,这个函数可以在某个地方挂起去执行别的,并且可以返回挂起处继续执行,线程数量越多协程的性能优势越明显,多进程和协程的组合能充分利用计算机的多核处理。

12.2 协程

Python对协程的支持常用的有yieldgreenlet gevent三种方式,进行协程的调用。

yeild 演示如下 -

from time import sleep


def study_math():
    for _ in range(5):
        print("study_math")
        yield
        sleep(0.5)


def study_chemisty():
    for _ in range(5):
        print("study_chemisty")
        yield
        sleep(0.5)


def main():
    study1 = study_math()
    study2 = study_chemisty()
    for _ in range(3):
        study1.__next__()
        study2.__next__()


if __name__ == '__main__':
    main()

上面脚本的执行结果 -

study_math
study_chemisty
study_math
study_chemisty
study_math
study_chemisty

greenlet 演示如下 -

对协程进行简单封装,该模块需要重新安装,在cmd命令行长输入“pin install greenlet ”即可

from time import sleep
from greenlet import greenlet


def study_math():
    for _ in range(3):
        print("study_math")
        glt2.switch()
        sleep(0.5)


def study_chemisty():
    for _ in range(3):
        print("study_chemisty")
        glt1.switch()
        sleep(0.5)


glt1 = greenlet(study_math)
glt2 = greenlet(study_chemisty)
glt1.switch()
glt2.switch()

上面脚本的执行结果 -

study_math
study_chemisty
study_math
study_chemisty
study_math
study_chemisty

gevent 演示如下 -

greenlet虽然实现协程,但需要手动切换,相当麻烦;这里提供gevent自动切换,在cmd命令行长输入“pin install gevent”即可。

在gevent协程中,协程只有遇到gevent指定类型的阻塞才能跳转到其他协程,因此,我们希望将普通的IO阻塞行为转换为可以触发gevent协程跳转的阻塞,以提高执行效率。

这里重点介绍 monkey 函数

from gevent import monkey
import gevent
from urllib.request import urlopen

monkey.patch_all()


def f(url):
    print('GET: %s' % url)
    resp = urlopen(url)
    data = resp.read()
    print('%d bytes received from %s.' % (len(data), url))


if __name__ == '__main__':
    gevent.joinall([
        gevent.spawn(f, 'https://www.tianya.cn'),
        gevent.spawn(f, 'https://www.yahoo.com/'),
        gevent.spawn(f, 'https://www.baidu.com/'),
    ])

上面脚本的执行结果 -

GET: https://www.tianya.cn
GET: https://www.yahoo.com/
GET: https://www.baidu.com/
227 bytes received from https://www.baidu.com/.
7646 bytes received from https://www.tianya.cn.
3369 bytes received from https://www.yahoo.com/.

12.3 协程池

gevent 常用方法如下 -

名称

说明

gevent.spawn()

创建一个普通的Greenlet对象并切换

gevent.spawn_later(seconds=3)

延时创建一个普通的Greenlet对象并切换

gevent.spawn_raw()

创建的协程对象属于一个组

gevent.getcurrent()

返回当前正在执行的greenlet

gevent.joinall(jobs)

将协程任务添加到事件循环,接收一个任务列表

gevent.wait()

可以替代join函数等待循环结束,也可以传入协程对象列表

gevent.kill()

杀死一个协程

gevent.killall()

杀死一个协程列表里的所有协程

monkey.patch_all()

非常重要,自动将 python 的一些标准模块替换成 gevent 框架设置强制切换的时间

sys.setcheckinterval(n)

每n条执行尝试进行线程切换,n必须是int

sys.getswitchinterval()

默认5ms切换

名称

说明

job

Greenlet(target0, 3) Greenlet对象创建

job.start()

将协程加入循环并启动协程

job.start_later(3)

延时启动

job.join()

等待任务完成

job.get()

获取协程返回的值

job.dead()

判断协程是否死亡

job.kill()

杀死正在运行的协程并唤醒其他的协程,这个协程将不会再执行

job.ready()

任务完成返回一个真值

job.successful()

任务成功完成返回真值,否则抛出错误

job.loop

时间循环对象

job.value

获取返回的值

job.exception

如果运行有错误,获取它异常信息

import gevent
from gevent import socket
from gevent.pool import Pool

N = 1000
# limit ourselves to max 10 simultaneous outstanding requests
pool = Pool(2)
finished = 0


def job(url):
    global finished
    try:
        try:
            ip = socket.gethostbyname(url)
            print("{url} = {ip}")
        except socket.gaierror as e:
            print(f"{url} failed with {e}")
    finally:
        finished += 1


with open("urls.txt", 'r') as f:
    with gevent.Timeout(10, False):
        for x in f:
            line = x.rstrip('\r\n')
            pool.spawn(job, f"{line}")
        pool.join()

print(f"finished within 10 seconds: {finished}/{N}")

12.4 总结

协程常用于IO密集型工作,例如网络资源请求等;而进程、线程常用于计算密集型工作,例如科学计算、人工神经网络等。