三十二、Python之协程技术

基础概念

    1. 定义:

        纤程,微线程。是允许在不同入口点不同位置暂停或开始的计算机程序,简单来说,协程就是可以暂停执行的函数。

    2. 协程原理 :

        记录一个函数的上下文,协程调度切换时会将记录的上下文保存,在切换回来时进行调取,恢复原有的执行内容,以便从上一次执行位置继续执行。协程本质上就是一个线程,以前多线程任务的切换是由操作系统控制的,遇到I/O阻塞就自动切换,现在我们用协程的目的就是较少操作系统切换的开销(开关线程,创建寄存器、堆栈等,在他们之间进行切换等),在我们自己的程序(应用层面)里面来控制任务的切换。对于单线程下,我们不可避免程序中出现io操作,但如果我们能在自己的程序中(即用户程序级别,而非操作系统级别)控制单线程下的多个任务能在一个任务遇到io阻塞时就切换到另外一个任务去计算,这样就保证了该线程能够最大限度地处于就绪态,即随时都可以被cpu执行的状态,相当于我们在用户程序级别将自己的io操作最大限度地隐藏起来,从而可以迷惑操作系统,让其看到:该线程好像是一直在计算,io比较少,从而更多的将cpu的执行权限分配给我们的线程。

    3. 协程优缺点

        优点

          【1】协程完成多任务占用计算资源很少

          【2】由于协程的多任务切换在应用层完成,因此切换开销少,最大限度地利用cpu

          【3】协程为单线程程序,无需进行共享资源同步互斥处理

        缺点

          协程的本质是一个单线程,无法利用计算机多核资源

扫描二维码关注公众号,回复: 9500419 查看本文章

标准库协程的实现

    python3.5以后,使用标准库asyncioasync/await 语法来编写并发代码。asyncio库通过对异步IO行为的支持完成python的协程。虽然官方说asyncio是未来的开发方向,但是由于其生态不够丰富,大量的客户端不支持awaitable需要自己去封装,所以在使用上存在缺陷。更多时候只能使用已有的异步库(asyncio等),功能有限。

示例:

import asyncio


async def fun1():
    print("Start1")
    # 遇到阻塞跳出
    await asyncio.sleep(3)
    print("end1")


async def fun2():
    print("Start2")
    # 遇到阻塞跳出
    await asyncio.sleep(2)
    print("end2")


cor1 = fun1()
cor2 = fun2()

tasks = [asyncio.ensure_future(cor1),
         asyncio.ensure_future(cor2)]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

# Start1
# Start2
# end2
# end1

第三方协程模块

    【1】greenlet模块

            安装 : sudo pip3 install greenlet

            函数:

g = greenlet.greenlet(func)

功能:创建协程对象

参数:协程函数

返回值:greenlet 协程函数对象

g.switch()

功能:选择要执行的协程函数

示例:
"""
    协程行为展示
"""

from greenlet import greenlet


def fun1():
    print("执行fun1")
    # 跳转执行fun2
    gr2.switch()
    print("结束fun1")
    # 跳回fun2继续往下执行,而不是从fun2第一行开始
    gr2.switch()


def fun2():
    print("执行fun2")
    # 跳回fun1继续往下执行,而不是从fun1第一行开始
    gr1.switch()
    print("结束fun2")


# 将函数变为协成函数
gr1 = greenlet(fun1)
gr2 = greenlet(fun2)
gr1.switch()

【2】gevent模块

           安装:sudo pip3 install gevent

           函数:

gevent.spawn(func,argv)

功能: 生成协程对象

参数:func 协程函数

           argv 给协程函数传参(不定参)

返回值: 协程对象

gevent.joinall(list,[timeout])

功能: 阻塞等待协程执行完毕

参数:list 协程对象列表

          timeout 超时时间

gevent.sleep(sec)

功能: gevent睡眠阻塞

参数:睡眠时间

* gevent协程只有在遇到gevent指定的阻塞行为时才会自动在协程之间进行跳转,如gevent.joinall(),gevent.sleep()带来的阻塞

         monkey脚本

            作用:

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

            转换方法:

                  gevent 提供了一个脚本程序monkey,可以修改底层解释IO阻塞的行为,将很多普通阻塞转换为gevent阻塞。  

            使用方法:

1】 导入monkey

from gevent import monkey

2】 运行相应的脚本,例如转换socket中所有阻塞

monkey.patch_socket()

3】 如果将所有可转换的IO阻塞全部转换则运行all

monkey.patch_all()

4】 注意:脚本运行函数需要在对应模块导入前执行

示例:

"""
    基于协程的tcp并发
"""
import gevent
from gevent import monkey

monkey.patch_socket()  # 执行脚本修改socket阻塞行为
from socket import *


def handle(c):
    while True:
        data = c.recv(1024).decode()  # 属于协程阻塞,不影响其他客户端
        if not data:
            return
        print(data)
        c.send(b'OK')


# 创建套接字
s = socket()
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind(('127.0.0.1', 8888))
s.listen(5)

# 循环接收来自客户端请求
while True:
    c, addr = s.accept()
    print("Connect from", addr)
    # handle(c)  # 循环方案
    gevent.spawn(handle, c)  # 协程方案
 
发布了39 篇原创文章 · 获赞 10 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/chiaotien/article/details/104542810
今日推荐