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

多协程确实快好多

相关文章
|
1月前
|
安全 调度 Python
探索Python中的并发编程:协程与多线程的比较
本文将深入探讨Python中的并发编程技术,重点比较协程与多线程的特点和应用场景。通过对协程和多线程的原理解析,以及在实际项目中的应用案例分析,读者将能够更好地理解两种并发编程模型的异同,并在实践中选择合适的方案来提升Python程序的性能和效率。
|
19小时前
|
开发者 Python
探索 Python 中的协程:从基本概念到实际应用
在现代编程中,异步处理变得越来越重要,Python 通过其内置的协程提供了强大的工具来简化这一过程。本文将深入探讨 Python 中的协程,从基本概念出发,逐步展示其实际应用,并通过具体代码示例帮助你掌握这种技术。
|
6天前
|
数据挖掘 调度 开发者
Python并发编程的艺术:掌握线程、进程与协程的同步技巧
并发编程在Python中涵盖线程、进程和协程,用于优化IO操作和响应速度。`threading`模块支持线程,`multiprocessing`处理进程,而`asyncio`则用于协程。线程通过Lock和Condition Objects同步,进程使用Queue和Pipe通信。协程利用异步事件循环避免上下文切换。了解并发模型及同步技术是提升Python应用性能的关键。
26 5
|
8天前
|
调度 开发者 UED
探索Python中的异步编程:从回调到协程
【6月更文挑战第14天】本文深入探讨了Python异步编程的演变历程,从最初的回调函数到现代的协程模型。我们将通过具体示例,展示如何利用asyncio库提升程序的执行效率和响应能力。文章旨在为读者提供一个清晰的异步编程发展脉络,并指导他们如何在项目中实际应用这些技术。
|
18天前
|
JavaScript 前端开发 程序员
Python协程与asyncio
理解Python中的协程,我们需从其底层原理开始,逐步深入。协程的核心在于控制流的非阻塞式管理,它允许在单一线程内实现并发处理,通过事件循环和协作式多任务来提高效率。
|
6天前
|
Java 开发者 计算机视觉
探索Python中的并发编程:线程与协程
本文将深入探讨Python中的并发编程,重点比较线程和协程的工作机制、优缺点及其适用场景,帮助开发者在实际项目中做出更明智的选择。
|
1月前
|
调度 Python
探索Python中的异步编程:从回调到协程
本文将介绍Python中的异步编程技术,从最初的回调函数到现代的协程模型。通过对比传统的同步编程方式和异步编程的优劣势,我们深入探讨了Python中异步编程的实现原理,以及如何利用asyncio库和async/await关键字来构建高效的异步应用程序。最后,我们还将讨论一些异步编程的最佳实践和常见问题的解决方法。
|
1月前
|
Python
Python中的协程:异步编程的利器
Python中的协程:异步编程的利器
20 1
|
29天前
|
消息中间件 安全 调度
基于Python的性能优化(线程、协程、进程)
一、多线程 在CPU不密集、IO密集的任务下,多线程可以一定程度的提升运行效率。