Python协程原理全面分析

Python 协程原理全面分析

在介绍Python协程原理之前,需要先了解一些概念:

  • 并发:同时处理多个任务。
  • 并行:同时处理多个任务并使它们同时运行。关注于任务的执行,强调在物理上同时运行多个任务。
  • 同步:任务按照一定的顺序进行,只有先完成前面任务才能完成后面任务。
  • 异步:不按照任务排定的先后顺序进行,而是根据情况随时安排执行任务。异步任务可以在等待IO的过程中进行其他任务,从而提高程序效率。

协程是为了处理异步的一种技术,协程比线程的切换更加轻量级,能够处理大量IO操作,提高程序的并发性和性能。

什么是协程

协程(Coroutine)是一种用户态的轻量级线程。用户可以自行控制协程的启动、暂停、恢复和终止。

协程的本质是控制流的上下文切换。

协程的启动、恢复和终止由用户直接控制,不需要操作系统的参与,避免了切换进程或线程的开销,因此协程的切换非常快速,有利于实现高效率的并发程序。

Python协程的实现

Python实现协程的方式分为三种:

  • 生成器(generator);
  • asyncio库;
  • gevent库。

这里我们主要介绍使用生成器实现协程。

生成器实现协程

使用生成器实现协程,需要用到两个关键字:yieldsend()

yield 是将一个函数变为生成器函数,可以将代码执行到 yield 处时中断函数,并返回一个值暂停函数;

send() 是从生成器的外部向其中传入一个值,继续执行被中断的代码。

在协程中,yieldsend() 的搭配使用实现协程的切换。

首先,定义一个协程函数:

def coroutine_func():
    print('Coroutine started.')
    while True:
        x = yield
        print('Got:', x)

在该函数中使用 yield 关键字,使该函数变为生成器函数。

函数中的 yield 实现中断函数并返回一个值,而 x = yield 则将生成器函数定义为一个可接收外部传值的函数。

接下来,我们通过以下步骤使用上面定义的协程函数:

  1. 使用 next() 函数启动协程;
  2. 使用 send() 函数向协程发送值;
  3. 使用 throw() 函数关闭协程。
# 启动生成器
coroutine = coroutine_func()
next(coroutine)

使用 send() 函数向协程发送值:

coroutine.send('Hello')
coroutine.send('World')

最后,关闭协程:

coroutine.throw(StopIteration)

执行结果:

Coroutine started.
Got: Hello
Got: World
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

协程函数被成功启动并执行到第一个 yield 处,接着使用 send() 函数发送了两个值并分别得到输出,最终使用 throw() 函数关闭了协程。

使用协程的优点

避免频繁的IO调用

对于IO密集型的操作(如文件、网络IO等),使用传统的同步IO会导致程序长时间等待,浪费CPU时间的开销。而使用异步IO可以在等待IO操作完成的过程中,继续进行其他操作,从而提高程序的并发性和性能。

修复回调地狱

使用回调函数处理异步操作会导致函数嵌套层数增加,代码越来越难以维护的问题。而使用协程可以避免这种情况。

示例说明

示例一:爬取豆瓣图书数据

以豆瓣图书数据为例,使用协程实现异步爬取数据,加快爬取速度。

首先,我们需要安装 requestsbeautifulsoup4fake_useragent 库。

定义一个获取豆瓣图书信息的函数,该函数包含三个协程函数:

  1. fetch_content,获取HTTP响应内容;
  2. parse_html,解析HTML文本;
  3. save_csv,保存数据到CSV文件中。
import csv
import random
import time

import requests
from bs4 import BeautifulSoup
from fake_useragent import UserAgent


def fetch_content(url):
    headers = {
        'User-Agent': UserAgent().random,
        'Referer': 'https://book.douban.com/',
        'Connection': 'keep-alive'
    }
    response = requests.get(url, headers=headers)
    return response.text


def parse_html(html):
    soup = BeautifulSoup(html, 'lxml')
    for item in soup.select('li.subject-item'):
        uid = item.select_one('div.info label.uid').text.strip()
        title = item.select_one('div.info h2 a').text.strip()
        authors = item.select_one('div.info div.pub').text.strip().split('/')[0]
        rating = item.select_one('div.info div.star span.rating_nums').text.strip()
        yield uid, title, authors, rating


def save_csv(filename, data):
    with open(filename, 'a', encoding='utf-8-sig', newline='') as f:
        writer = csv.writer(f)
        writer.writerows(data)

定义一个协程函数,接收书籍的url和CSV文件名。该函数先获取豆瓣图书的HTML并解析出数据,然后将数据写入CSV文件中。

def crawl_book(url, filename):
    html = yield from fetch_content(url)
    data = list(parse_html(html))
    save_csv(filename, data)
    print(f'Crawled {url} successfully.')

接下来,我们在主程序中使用协程爬取豆瓣图书数据。

首先,定义待爬取的图书列表和CSV文件名。

urls = [f'https://book.douban.com/tag/编程?start={i*20}' for i in range(5)]
filename = 'books.csv'

定义一个协程任务调度函数,使用 asyncio 库实现。

import asyncio


async def task(urls, filename):
    tasks = [crawl_book(url, filename) for url in urls]
    await asyncio.gather(*tasks)

使用以下代码启动协程任务调度函数。

start_time = time.time()
loop = asyncio.get_event_loop()
loop.run_until_complete(task(urls, filename))
elapsed_time = time.time() - start_time
print(f'Total time elapsed: {elapsed_time:.2f}s')

运行程序后,完成爬取任务,并将数据保存在 books.csv 文件中。运行时间大幅度降低,可以看出协程的优势。

示例二:实现异步UDP服务器

以下示例使用Python3.7+提供的新语法 asyncawait 实现异步UDP服务器。

import asyncio


async def handle_datagram(remote_addr, message):
    # 模拟消息处理的耗时过程
    await asyncio.sleep(0.1)
    print(f'Received a message {message.decode()} from {remote_addr[0]}:{remote_addr[1]}')


async def listen_udp_server(host, port):
    # 创建UDP服务器
    server = await asyncio.create_datagram_endpoint(
        protocol_factory=asyncio.DatagramProtocol,
        local_addr=(host, port)
    )

    print(f'Server started and listening on {host}:{port}.')

    # 监听客户端发送的UDP数据包
    async for datagram, remote_addr in server[0]:
        asyncio.create_task(handle_datagram(remote_addr, datagram))


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    try:
        loop.run_until_complete(listen_udp_server('127.0.0.1', 8888))
    except KeyboardInterrupt:
        pass
    finally:
        loop.close()

运行以上代码后,会启动一个监听本地8888端口的异步UDP服务器,并接收客户端发送的消息。当收到消息后,调用 handle_datagram 函数处理,模拟一个耗时的操作。

总结

协程是一种效率较高的异步编程方式,可以充分发挥计算机的性能,提高程序的并发性和性能。Python提供了多种实现协程的方式,如使用生成器实现、使用asyncio库实现、使用gevent库实现等。

在使用协程时,需要合理使用 yieldsend() 函数,避免死锁和资源泄漏等问题。同时也需要注意调整好协程任务的数量和优先级。

最后,协程的编写需要一定的技巧和经验,希望本文的介绍能够对读者有所帮助。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Python协程原理全面分析 - Python技术站

(0)
上一篇 2023年5月19日
下一篇 2023年5月19日

相关文章

  • Python使用urllib2获取网络资源实例讲解

    欢迎来到本网站,本文将为大家详细讲解使用Python的urllib2库获取网络资源的过程。使用urllib2库可以轻松地与网络进行交互,获取网页数据,进行Post请求等操作。 urllib2库的常见用法 GET请求 获取一个远程网页数据是最常见也是最基础的使用方式。使用Python的urllib2库可以轻松地实现。 import urllib2 url = …

    python 2023年6月3日
    00
  • Python字符串匹配之6种方法的使用详解

    以下是详细讲解“Python字符串匹配之6种方法的使用详解”的完整攻略,包括6种方法的介绍、使用方法、示例说明和注意事项。 6种介绍 在Python中,有多种方法可以进行字符串匹配。下面介绍6种常用的方法: 使用in关键字 使用find()函数 使用index()函数 使用re模块的search()函数 使用re模块的match()函数 使用re模块的fin…

    python 2023年5月14日
    00
  • python的函数和方法(中)

    Python的函数和方法(中): 在Python中,函数和方法是两个重要的概念。函数是一个独立的代码块,可被多次调用,用于完成一定的功能。方法是对象中的函数,它是一个与对象相关联的函数。本文将探讨Python中函数和方法的更多知识点。 函数参数: Python中函数的参数可以有默认值,也可以为可变参数。默认值参数表示,当函数没有传递这个参数时,它使用默认值。…

    python 2023年6月5日
    00
  • 如何利用python将一个py文件变成一个软件详解

    将Python程序转化为独立可执行文件可以方便程序的分发和使用。下面是一些关于如何利用Python将一个.py文件打包成应用程序的详细攻略。 一、PyInstaller的安装 我们可以使用PyInstaller这个第三方库来将Python程序转化为独立可执行文件。首先需要安装PyInstaller,安装方式如下: pip install pyinstalle…

    python 2023年5月18日
    00
  • python中pop()函数的语法与实例

    当我们在Python中使用列表时,pop()函数是一个很有用的函数。pop函数用于取出一个指定索引的元素,并将该元素从列表中删除。在该函数的使用中,我们可以提供一个信息:指定要删除元素的索引。 下面是该函数的详细语法: list.pop([index]) 其中,方括号表示可选参数。index表示该参数的位置,它是从0开始计数的。如果没有用方括号表示,那么该函…

    python 2023年5月13日
    00
  • Python生成指定数量的优惠码实操内容

    生成指定数量的优惠码,一般使用随机数的方式即可实现。下面是详细的操作步骤。 步骤1:导入相关库 我们需要导入 random、string 库,其中 random 库用于生成随机数,而 string 库则用于生成随机的字符串。 import random import string 步骤2:设置优惠码的长度和数量 # 设置优惠码的长度 CODE_LENGTH …

    python 2023年6月3日
    00
  • 基于matplotlib xticks用法详解

    确保你已经正确安装了matplotlib库。matplotlib是一个Python第三方库,可用于绘制各种图表和图形。在本攻略中,我们将深入了解matplotlib的xticks用法,用于创建、定制和移动轴刻度。 使用xticks函数来设置轴刻度 在matplotlib中,我们可以使用xticks()函数来设置轴刻度。该函数允许我们用数字或字符串数组设置自定…

    python 2023年5月18日
    00
  • Python进行密码学反向密码教程

    Python进行密码学反向密码教程 本教程将介绍如何使用Python进行密码学反向密码。通过本教程,您将了解基本的密码学概念以及如何使用Python语言来编写程序来对密码进行反向分析。 什么是密码学反向密码? 密码学反向密码是一种通过猜测密码、穷举密码、绕过密码或者对密码进行加密解密操作来获取或者更改加密信息的技术。密码学反向密码是黑客攻击和网络安全测试中非…

    python 2023年6月5日
    00
合作推广
合作推广
分享本页
返回顶部