深入探索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秒左右

多协程确实快好多

相关文章
|
4天前
|
网络协议 调度 开发者
python中gevent基于协程的并发编程模型详细介绍
`gevent`是Python的第三方库,提供基于协程的并发模型,适用于I/O密集型任务的高效异步编程。其核心是协程调度器,在单线程中轮流执行多个协程,通过非阻塞I/O实现高并发。主要特点包括协程调度、事件循环的I/O模型、同步/异步编程支持及易用性。示例代码展示了一个使用`gevent`实现的异步TCP服务器,当客户端连接时,服务器以协程方式处理请求,实现非阻塞通信。
17 0
|
4天前
|
安全 调度 Python
探索Python中的并发编程:协程与多线程的比较
本文将深入探讨Python中的并发编程技术,重点比较协程与多线程的特点和应用场景。通过对协程和多线程的原理解析,以及在实际项目中的应用案例分析,读者将能够更好地理解两种并发编程模型的异同,并在实践中选择合适的方案来提升Python程序的性能和效率。
|
4天前
|
调度 Python
探索Python中的异步编程:从回调到协程
本文将介绍Python中的异步编程技术,从最初的回调函数到现代的协程模型。通过对比传统的同步编程方式和异步编程的优劣势,我们深入探讨了Python中异步编程的实现原理,以及如何利用asyncio库和async/await关键字来构建高效的异步应用程序。最后,我们还将讨论一些异步编程的最佳实践和常见问题的解决方法。
|
4天前
|
Python
Python中的协程:异步编程的利器
Python中的协程:异步编程的利器
17 1
|
4天前
|
调度 数据库 Python
探索Python中的异步编程:从回调到协程
本文将探讨Python中异步编程的演变过程,从最初的回调函数到现代的协程机制。我们将深入了解异步编程的原理、优势以及如何使用Python的asyncio库来实现高效的异步程序。通过本文,读者将了解到异步编程的基本概念、常见的应用场景,以及如何利用Python的强大功能来提升程序的性能和可维护性。
|
4天前
|
关系型数据库 数据处理 数据库
Python中的异步编程与协程详解
Python作为一门流行的编程语言,其异步编程与协程机制在近年来备受关注。本文将深入探讨Python中的异步编程概念、asyncio模块的运用以及协程的原理和使用方法,帮助读者更好地理解和应用异步编程技术。
|
4天前
|
Python
探索Python中的异步编程:从回调到协程
传统的Python编程模式中,使用回调函数处理异步任务是常见的做法。然而,随着Python 3.5引入的asyncio模块,异步编程有了更加优雅和高效的解决方案:协程。本文将深入探讨Python中异步编程的发展历程,从回调函数到协程的演变,并介绍如何使用asyncio模块来实现异步任务,提高程序的性能和响应速度。
|
4天前
|
调度 Python
Python多线程、多进程与协程面试题解析
【4月更文挑战第14天】Python并发编程涉及多线程、多进程和协程。面试中,对这些概念的理解和应用是评估候选人的重要标准。本文介绍了它们的基础知识、常见问题和应对策略。多线程在同一进程中并发执行,多进程通过进程间通信实现并发,协程则使用`asyncio`进行轻量级线程控制。面试常遇到的问题包括并发并行混淆、GIL影响多线程性能、进程间通信不当和协程异步IO理解不清。要掌握并发模型,需明确其适用场景,理解GIL、进程间通信和协程调度机制。
38 0
|
4天前
|
存储 Python
python使用gevent库来创建协程,并通过协程实现并发执行不同的任务
```markdown 这段Python代码利用`gevent`库实现并发执行协程。定义了两个打印函数`f1`和`f2`,分别输出&quot;csdn&quot;和&quot;yyds&quot;。代码首先创建列表`t_l`,并启动5个`f1`协程,将其加入列表并等待所有协程完成。随后,同样方式启动5个`f2`协程,存入`t1_l`列表并等待执行完毕。整体展示了`gevent`的协程并发操作。 ```
14 1