python 协程详解

大家好,我是剑南。

今天为大家带来的内容是协程概念的详解以及配套实战内容。

协程

协程的概念

协程,又称作是微线程,协程是一种用户态的轻量级编程。

其实,怎么样来理解会更好一些呢?

我这样说吧,线程是系统级别的,它们由操作系统调度;协程是程序级别的,由程序员根据自己的需求去调度。我们把线程中一个个函数叫做子程序,那么子程序在执行过程中可以中断,然后去执行别的子程序;别的子程序也可以中断回来继续执行之前的子程序,这就是协程。也就是说,在同一个线程下的一段代码,在执行的过程中,有可能会发生中断,然后跳去执行另一段代码,当再次回来执行之前的代码块的时候,就从之前中断的地方开始执行。

协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切换回来的时候,恢复之前保存的寄存器上下文和栈。因此:协程可以保留上一次调用时的状态,每次过程重入的时候,就相当于进入上一次调用的状态。换种说法就是进入上一次离开时所处逻辑留的位置。

协程的优点

  • 协程不需要像线程一样存在上下文切换的开销,避免了无意义的调度,由此可以提高性能。
  • 无需原子操作锁定及同步的开销
  • 方便切换控制流,简单化编程模型
  • 并发+高拓展性+低成本:一个CPU支持上万的协程都不是问题,所有很合适用于高并发处理。

协程的缺点

  • 无法利用多核资源:协程的本质是单线程,因此它不能将单个CPU的多个核都用上,协程需要和进程配合才能运行在多核CPU上。
  • 进行阻塞操作时会阻塞掉整个程序

python如何实现协程

协程是通过async/await语法进行声明,是编写asyncio应用的推荐方式。具体代码,如下所示:

import asyncio


async def main():
 print('hello')
 await asyncio.sleep(5)
 print('world')


asyncio.run(main())

关键字await有两个作用,一是作为求值的关键字,二是将异步操作变为同步操作;如果方法中使用了await,那么在方法的前面就必须加上async。

一般来说,正常的函数在执行的时候是不会被中断的,所有想要写一个可以中断的函数,就需要添加async。

像上面的代码,await asyncio.sleep(5),就是说这个程序被挂起5秒,5秒后再回来执行程序。

await用来声明程序挂起,比如异步程序执行到某一步时需要等待很长的时间,就将挂起,去执行其他异步程序,当挂起条件消失之后,不管当前程序是否执行完毕,都必须退出,去执行之前的程序。

asyncio.run()函数用来运行最高层级的入口点“main()”函数。

等待协程

import asyncio
import time


async def say_after(delay, what):
 await asyncio.sleep(delay)
 print(what)


async def main():
 print(f'start at {time.strftime("%X")}')

 await say_after(2'hello')
 await say_after(2'world')

 print(f'finish at {time.strftime("%X")}')

asyncio.run(main())

运行结果,如下所示:

start at 13:59:21
hello
world
finish at 13:59:25

多任务协程

使用create_task()函数用来并发运行作为asyncio任务的多个协程。如何并发运行两个任务呢?

具体代码,如下所示:

import asyncio
import time


async def say_after(delay, what):
 await asyncio.sleep(delay)
 print(what)


async def main():
 task1 = asyncio.create_task(say_after(2'hello'))
 task2 = asyncio.create_task(say_after(2'world'))
 print(f'start at {time.strftime("%X")}')

 await task1
 await task2

 print(f'finish at {time.strftime("%X")}')

asyncio.run(main())

运行结果,如下所示:

start at 16:28:53
hello
world
finish at 16:28:55

预期的显示输出时间比之前快了2秒。

可等待对象

如果一个对象可以在await语句中使用,那么它就是可等待对象,可等待对象一共有三种类型:协程、任务与Future。

协程属于python的可等待对象,因此可以在其他协程中被等待,具体代码,如下所示:

import asyncio


async def nested():
 return 42


async def main():
 nested() # 报错
 print(await nested()) # 正常


asyncio.run(main())

对于上面的代码,有一点要注意,那就是当我们创建了一个协程方法时,需要调用,就一定要用关键字await。

协程函数指的是:定义形式为async def的函数。

协程对象指的是:调用协程函数所调用的对象。

任务用来并行的调度协程,当一个协程通过create_task()等函数封装为一个任务,该协程会被自动调度执行。

具体代码,如下所示:

import asyncio


async def nested():
 return 42


async def main():
 task = asyncio.create_task(nested())
 await task


asyncio.run(main())

Future是一种低层级的对象,表示一个异步操作的最终结果。当一个Future对象被等待,这意味着协程将保持等待直到该Future对象在其他地方操作完毕。通常情况下没有必要在应用级的代码中创建Future对象。

并发运行任务

具体代码,如下所示:

import asyncio


async def factorial(name, number):
 f = 1
 for i in range(1, number+1):
  print(f'Task {name}: Compute factorial({number}), currrently i = {i}...')
  await asyncio.sleep(1)
  f *= i
 print(f'Task {name}: factorial({number}) = {f}')
 return f


async def main():
 L = await asyncio.gather(factorial('A'2), factorial('B'3), factorial('C'4))
 print(L)


asyncio.run(main())

运行结果,如下所示:

Task A: Compute factorial(2), currrently i = 1...Task B: Compute factorial(3), currrently i = 1...Task C: Compute factorial(4), currrently i = 1...Task A: Compute factorial(2), currrently i = 2...Task B: Compute factorial(3), currrently i = 2...Task C: Compute factorial(4), currrently i = 2...Task A: factorial(2) = 2Task B: Compute factorial(3), currrently i = 3...Task C: Compute factorial(4), currrently i = 3...Task B: factorial(3) = 6Task C: Compute factorial(4), currrently i = 4...Task C: factorial(4) = 24[2, 6, 24]

通过gather()方法,可以将一个协程对象作为任务调度,当所有的可等待对象都已经完成,结果将是由一个所有值聚合而成的列表。

实战-彼岸图网

python 协程详解
image-20210808025920195

如上图所示,本次的实战案例是爬取彼岸图网,网址如下:

https://pic.netbian.com/4kdongman/

本次实战,需要各位小伙伴们掌握xpath语法与正则表达式。

在本次项目中,最重要的就是找到4K图片的下载地址,这一点很重要。

python 协程详解

当我点击下载原图时,我便捕获到了真正的url地址,如下所示:

https://pic.netbian.com/downpic.php?id=27068&classid=66

这里有两个查询参数,一个是id,另一个是classid,对于这两个参数,我的第一想法是看看网页中是否存在这两个参数的值。

python 协程详解

从上图可以看到,在网页上存在这两个参数的值,那么只需要利用xpath与正则表达式即可。

创建协程函数

在这里,我主要有创建三个任务,一个是获取详情页的url,一个是获取图片的url,最后一个保存图片。

具体代码,如下所示:

async def get_url(url):
    proxies = {
        'http://''http://182.38.188.206:4210',
        'https://''http://182.38.188.206:4210'
    }
    headers = {
        'user-agent''Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36'
    }
    response = httpx.get(url, headers=headers, proxies=proxies)
    html = etree.HTML(response.text)
    hrefs = html.xpath('//ul[@class="clearfix"]/li/a/@href')
    hrefs = ['https://pic.netbian.com/' + href for href in hrefs]
    return hrefs


async def get_img_url(hrefs):
    args = []
    img_urls = []
    headers = {
        'user-agent''Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36'
    }
    proxies = {
        'http://''http://182.38.188.206:4210',
        'https://''http://182.38.188.206:4210'
    }
    for href in hrefs:
        response = httpx.get(href, headers=headers, proxies=proxies)
        html = etree.HTML(response.text)
        src = html.xpath('//script[last()]/@src')[0]
        arg = re.findall('.*?(classid=d+&id=d+).*?', src)[0]
        img_url = 'https://pic.netbian.com/downpic.php?' + arg
        img_urls.append(img_url)

    return img_urls


async def save_img(img_urls):
    headers = {
        'user-agent''Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36'
    }
    proxies = {
        'http://''http://182.38.188.206:4210',
        'https://''http://182.38.188.206:4210'
    }
    for img_url in img_urls:
        i = 1
        response = httpx.get(img_url, headers=headers, proxies=proxies)
        data = response.content
        with open(f'./picture/{i}.jpg''wb'as f:
            f.write(data)
        i += 1

创建任务

async def main():
    url = 'https://pic.netbian.com/4kdongman/'
    task1 = asyncio.create_task(get_url(url))
    hrefs = await task1
    task2 = asyncio.create_task(get_img_url(hrefs))
    img_urls = await task2
    print(img_urls)
    task3 = asyncio.create_task(save_img(img_urls))
    await task3

最后

本篇文章主要是想要大家掌握到协程的创建与使用,后期关于协程的更多用法,我会在遇到其他实例的时候再和大家做分享。

文章的每一个字都是我用心敲出来的,点个【赞】与【在看】,让我知道你和我一样都在努力学习。


原文始发于微信公众号(小志Codings):python 协程详解

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/236596.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!