深入探索Python中的协程

简介: 深入探索Python中的协程

协程

Hello,大家好,我是景天。今天我们来一起探讨一下Python的异步神技—协程。

协程也叫纤程: 协程是线程的一种实现方式.

指的是一条线程能够在多任务之间来回切换的一种实现.

对于CPU、操作系统来说,协程并不存在.

任务之间的切换会花费时间.

目前电脑配置一般线程开到200会阻塞卡顿.

1.协程是什么?

协程又称微线程,纤程。

它是比线程更小的执行单元,因为它自带CPU上下文。这样只要在合适的时机,我们可以把一个协程切换到另一个协程当中。

只要这个过程保存或恢复CPU上下文,那么程序就可以运行。

通俗的理解:在一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数中执行,

(注意不是通过调用函数的方式来实现),并且切换的次数以及什么时候再切换到原来的函数由开发者确定。

2.协程和线程有什么不同

那么这个过程看起来和线程差不多。其实不然, 线程切换从系统层面远不止保存和恢复 CPU上下文这么简单。

操作系统为了程序运行的高效性每个线程都有自己缓存Cache等等数据,操作系统还会帮你做这些数据的恢复操作。

所以线程的切换非常耗性能。但是协程的切换只是单纯的操作CPU的上下文,所以一秒钟切换个上百万次系统都抗的住。

3.协程的实现

协程帮助你记住哪个任务执行到哪个位置上了,并且实现安全的切换

一个任务一旦阻塞卡顿,立刻切换到另一个任务继续执行,保证线程总是忙碌的,更加充分的利用CPU,抢占更多的时间片

一个线程可以由多个协程来实现,协程之间不会产生数据安全问题

协程模块

# greenlet gevent的底层,协程,切换的模块

# gevent 直接用的,gevent能提供更全面的功能

进程是资源分配的最小单位

线程是程序调度的最小单位

协程是线程实现的具体方式

在进程一定的情况下,开辟多个线程,

在线程一定的情况下,创建多个协程,

以便提高更大的并行并发

协程是基于单线程实现的异步并发结构,可以让线程执行任务遇到阻塞时立马切换到其他任务,当前任务就绪后,接着执行。保证线程总是忙碌的,更加充分的利用CPU,抢占更多的时间片

(1) 用协程改写生产者消费者模型

def producer():
    for i in range(1000):
        yield i

def consumer(gen):
    for i in range(10):
        print(  next(gen)  )
        
gen = producer()
consumer(gen)
print("<==========>")
consumer(gen)
print("<==========>")
consumer(gen)

(2) greenlet 协程的早期版本

from greenlet import greenlet
import time
switch 可以切换任务,但是需要手动切换

def eat():
    print("eat1")
    g2.switch()
    time.sleep(3)
    print("eat2")
    
def play():
    print("play1")    
    time.sleep(3)
    print("play2")
    g1.switch()
#创建协程对象,把要执行的任务函数塞进去    
g1 = greenlet(eat)
g2 = greenlet(play)
g1.switch()

协程帮助切换任务,回头的时候还能记住代码执行状态

g1.switch()开始执行eat这个任务

在eat任务里面,遇到g2.switch(),开始执行play任务

在play任务里面,遇到g1.switch()开始回到eat任务上次切换的地方,继续往下执行

greenlet需要手动切换,很不方便

(3) 升级到gevent版本

需要安装,导入显示没有时,Windows直接安装

linux安装

pip3 install gevent

自动进行任务上的切换,但是不能识别阻塞

import gevent

def eat():
    print("eat1")
    gevent.sleep(3)
    # time.sleep(3)
    print("eat2")
    
def play():
    print("play1")
    gevent.sleep(3)    
    # time.sleep(3)
    print("play2")

#利用gevent.spawn创建协程对象g1
g1 = gevent.spawn(eat)   #参数是任务函数,任务函数的参数,可以跟多个参数
#利用gevent.spawn创建协程对象g2
g2 = gevent.spawn(play)
#如果不加join, 主线程直接结束任务,不会默认等待协程任务.

#阻塞,必须等待g1任务完成之后在放行
g1.join()
#阻塞,必须等待g2任务完成之后在放行
g2.join()

print("主线程执行结束 ....  ")

如果只是创建协程对象,任务有阻塞,协程不会运行,代码已结束。gevent()不识别阻塞

主线程默认不会等待当前线程中协程中的任务

所以为了让协程中的代码执行完,再执行主线程中的代码,需要join,必须等待协程中任务执行完在往下执行,但是只用时间阻塞,任务没有切换

使用gevent.sleep(),遇到就切换

(4) 协程的终极版本

引入猴子补丁,可以实现所有的阻塞全部识别

from gevent import monkey;monkey.patch_all()


import time
import gevent

def eat():
    print("eat1")
    time.sleep(3)
    print("eat2")
    
def play():
    print("play1")
    time.sleep(3)
    print("play2")


# 利用gevent.spawn创建协程对象g1
g1 = gevent.spawn(eat)
# 利用gevent.spawn创建协程对象g2
g2 = gevent.spawn(play)
# 如果不加join, 主线程直接结束任务,不会默认等待协程任务.

# 阻塞,必须等待g1任务完成之后在放行
g1.join()
# 阻塞,必须等待g2任务完成之后在放行
g2.join()

print(" 主线程执行结束 ... ")

利用monkey补丁,识别所有阻塞

导入monkey之后

执行monkey.patch_all()

把要导入的模块放入下面

也可以在导入monkey的时候加分号,放在一行写。然后下面导入模块 这样,各种阻塞都能识别,切换

导入monkey模块,必须放在最上面,其他模块如果放在其上面,导致报错

导入monkey放顶格,不再报错

gevent.spwn()创建协程到时候,可以带参数

可以使用gevent.joinall([g1,g2…])一次性join。不用一个个单独join

4.协程例子

# (1) spawn(函数,参数1,参数2,参数 .... ) 启动协程
# (2) join 阻塞,直到某个协程在任务执行完毕之后在放行
# (3) joinall 等待所有协程任务执行完毕之后放行;
      g1.join()  g2.join() <=> gevent.joinall( [g1,g2..] )
# (4) value 获取协程任务中的返回值 g1.value  g2.value
在线程池中,还可以通过submit对象,调用add_done_callback()来获取任务中的返回值


猴子补丁一定要在gevent之前引进来。最好把猴子补丁放在最前面

from gevent import monkey ; monkey.patch_all()
import gevent
import time
import requests


def eat():
    print("eat1 开始吃 ... ")
    time.sleep(1)
    print("eat2 继续吃 ... ")
    return "吃完了"    
    
def play():
    print("play1 开始玩 ... ")
    time.sleep(1)
    print("play2 继续玩 ... ")
    return "玩完了"

# 创建协程对象g1
g1 = gevent.spawn(eat)
# 创建协程对象g2
g2 = gevent.spawn(play)
# 等待所有协程任务执行完毕之后放行
gevent.joinall( [g1,g2] )
print("主线程执行结束 ... ")
# 获取协程任务中的返回值
print(g1.value)
print(g2.value)

带有参数的协程,并获取协程返回值

(1) 利用协程爬取数据

HTTP 状态码

200 ok

400 bad request

404 not found

import requests   #需要安装requests
response = requests.get("http://www.baidu.com")    #抓取网站一定得加协议,不然抓不了
#返回的是个类

# print(response ,type(response) )

# 获取状态码
print(response.status_code)
# 获取网页中的字符编码
res = response.apparent_encoding
print(res) # utf-8
# 设置编码集,防止乱码
response.encoding = res
# 获取网页内容
res = response.text
print(res)

一般要先获取网站原编码,然后根据网站原编码设置编码编码集,不然无脑设置成utf-8的话,原编码不是utf-8也会乱码

url_lst = [
    "http://www.baidu.com",
    "http://www.jd.com/",
    "http://www.taobao.com/",
    "http://www.amazon.cn/",
    "http://www.pinduoduo.com/",
    "http://www.4399.com/",
    "http://www.baidu.com",
    "http://www.jd.com/",
    "http://www.taobao.com/",
    "http://www.amazon.cn/",
    "http://www.pinduoduo.com/",
    "http://www.4399.com/",
    "http://www.baidu.com",
    "http://www.jd.com/",
    "http://www.taobao.com/",
    "http://www.amazon.cn/",
    "http://www.pinduoduo.com/",
    "http://www.4399.com/",
    "http://www.baidu.com",
    "http://www.jd.com/",
    "http://www.taobao.com/",
    "http://www.amazon.cn/",
    "http://www.pinduoduo.com/",
    "http://www.4399.com/",
    "http://www.baidu.com",
    "http://www.jd.com/",
    "http://www.taobao.com/",
    "http://www.amazon.cn/",
    "http://www.pinduoduo.com/",
    "http://www.4399.com/",
    "http://www.baidu.com",
    "http://www.jd.com/",
    "http://www.taobao.com/",
    "http://www.amazon.cn/",
    "http://www.pinduoduo.com/",
    "http://www.4399.com/",
    "http://www.baidu.com",
    "http://www.jd.com/",
    "http://www.taobao.com/",
    "http://www.amazon.cn/",
    "http://www.pinduoduo.com/",
    "http://www.4399.com/",
    "http://www.baidu.com",
    "http://www.jd.com/",
    "http://www.taobao.com/",
    "http://www.amazon.cn/",
    "http://www.pinduoduo.com/",
    "http://www.4399.com/",
    "http://www.baidu.com",
    "http://www.jd.com/",
    "http://www.taobao.com/",
    "http://www.amazon.cn/",
    "http://www.pinduoduo.com/",
    "http://www.4399.com/",
    "http://www.baidu.com",
    "http://www.jd.com/",
    "http://www.taobao.com/",
    "http://www.amazon.cn/",
    "http://www.pinduoduo.com/",
    "http://www.4399.com/",
    "http://www.baidu.com",
    "http://www.jd.com/",
    "http://www.taobao.com/",
    "http://www.amazon.cn/",
    "http://www.pinduoduo.com/",
    "http://www.4399.com/"
]


def get_url(url):
    response = requests.get(url)
    if response.status_code == 200:
        # print(response.text)
        pass


1. 正常爬取
startime = time.time()
for i in url_lst:
    get_url(i)
endtime = time.time()
print(endtime-startime) 

正常单线程爬取用时7.09112286567688秒

2.用协程的方法爬取数据
lst = []

startime = time.time()
#每个页面设置一个协程,多少个链接,创建多少个协程。当有网站阻塞,协程立马切到其他网站,等该网站就绪,协程再切回来

for i in url_lst:
    g = gevent.spawn(get_url , i)
    lst.append(g)
    
gevent.joinall( lst )
endtime = time.time()
print("主线程执行结束 ... 时间{}".format(endtime-startime)) 

用多协程爬取总用时0.8327083587646484。1秒左右

多协程确实快好多

相关文章
|
30天前
|
调度 Python
python知识点100篇系列(20)-python协程与异步编程asyncio
【10月更文挑战第8天】协程(Coroutine)是一种用户态内的上下文切换技术,通过单线程实现代码块间的切换执行。Python中实现协程的方法包括yield、asyncio模块及async/await关键字。其中,async/await结合asyncio模块可更便捷地编写和管理协程,支持异步IO操作,提高程序并发性能。协程函数、协程对象、Task对象等是其核心概念。
|
20天前
|
NoSQL 关系型数据库 MySQL
python协程+异步总结!
本文介绍了Python中的协程、asyncio模块以及异步编程的相关知识。首先解释了协程的概念和实现方法,包括greenlet、yield关键字、asyncio装饰器和async/await关键字。接着详细讲解了协程的意义和应用场景,如提高IO密集型任务的性能。文章还介绍了事件循环、Task对象、Future对象等核心概念,并提供了多个实战案例,包括异步Redis、MySQL操作、FastAPI框架和异步爬虫。最后提到了uvloop作为asyncio的高性能替代方案。通过这些内容,读者可以全面了解和掌握Python中的异步编程技术。
37 0
|
20天前
|
数据采集 缓存 程序员
python协程使用教程
1. **协程**:介绍了协程的概念、与子程序的区别、优缺点,以及如何在 Python 中使用协程。 2. **同步与异步**:解释了同步与异步的概念,通过示例代码展示了同步和异步处理的区别和应用场景。 3. **asyncio 模块**:详细介绍了 asyncio 模块的概述、基本使用、多任务处理、Task 概念及用法、协程嵌套与返回值等。 4. **aiohttp 与 aiofiles**:讲解了 aiohttp 模块的安装与使用,包括客户端和服务器端的简单实例、URL 参数传递、响应内容读取、自定义请求等。同时介绍了 aiofiles 模块的安装与使用,包括文件读写和异步迭代
22 0
|
1月前
|
数据处理 Python
深入探索:Python中的并发编程新纪元——协程与异步函数解析
深入探索:Python中的并发编程新纪元——协程与异步函数解析
27 3
|
2月前
|
Python
Python中的异步编程与协程实践
【9月更文挑战第28天】本文旨在通过一个简单易懂的示例,介绍如何在Python中利用asyncio库实现异步编程和协程。我们将通过代码示例来展示如何编写高效的并发程序,并解释背后的原理。
|
2月前
|
数据库 开发者 Python
实战指南:用Python协程与异步函数优化高性能Web应用
在快速发展的Web开发领域,高性能与高效响应是衡量应用质量的重要标准。随着Python在Web开发中的广泛应用,如何利用Python的协程(Coroutine)与异步函数(Async Functions)特性来优化Web应用的性能,成为了许多开发者关注的焦点。本文将从实战角度出发,通过具体案例展示如何运用这些技术来提升Web应用的响应速度和吞吐量。
29 1
|
2月前
|
调度 Python
揭秘Python并发编程核心:深入理解协程与异步函数的工作原理
在Python异步编程领域,协程与异步函数成为处理并发任务的关键工具。协程(微线程)比操作系统线程更轻量级,通过`async def`定义并在遇到`await`表达式时暂停执行。异步函数利用`await`实现任务间的切换。事件循环作为异步编程的核心,负责调度任务;`asyncio`库提供了事件循环的管理。Future对象则优雅地处理异步结果。掌握这些概念,可使代码更高效、简洁且易于维护。
26 1
|
2月前
|
调度 开发者 Python
探索Python中的异步编程:理解asyncio和协程
【9月更文挑战第22天】在现代软件工程中,异步编程是提升应用性能的关键技术之一。本文将深入探讨Python语言中的异步编程模型,特别是asyncio库的使用和协程的概念。我们将了解如何通过事件循环和任务来处理并发操作,以及如何用协程来编写非阻塞的代码。文章不仅会介绍理论知识,还会通过实际的代码示例展示如何在Python中实现高效的异步操作。
|
1月前
|
数据采集 调度 Python
Python编程异步爬虫——协程的基本原理(一)
Python编程异步爬虫——协程的基本原理(一)
|
1月前
|
数据采集 Python
Python编程异步爬虫——协程的基本原理(二)
Python编程异步爬虫——协程的基本原理(二)