一、协程介绍##
1、概念
协程是单线程下的并发,又称为微线程
它是一种用户态的轻量级线程
协程能保留上一次调用时的状态
进程和线程时通过操作系统(cpu来调度),而协程是由程序员来调度控制的
2、协程的意义:
多线程:分时切片,并发操作,线程切换需要耗时(保存状态)
协程:    只使用一个线程,在一个线程中规定某个代码块的执行顺序
3、应用场景:
因为Python有GIL(全局解释器锁)的存在,在同一时间内实际只有一个线程在工作(IO操作保存状态、切换耗时),多线程的执行效率比协程会更低。

84a91f4f66a9d235ca5178ef36e73f4.png
- 高并发I/O场景:如爬虫、聊天应用、HTTP服务器。
- 异步编程:如Python中的asyncio、JavaScript中的async/await。
- 数据流处理:处理大量并发任务时,协程能显著提高效率
 4、协程特点:
- 轻量级:协程无需像线程一样依赖操作系统进行上下文切换,由程序自行调度
- 非抢占式: 协程是协作式的,由代码显式控制何时出让执行器
- 单线程多任务:在一个线程内实现多个任务之间的切换
- 异步和高效:协程可以暂停当前任务并切换到其他任务,适合 I/O 密集型任务
二、协程创建
1、yield
1)概念:
- yield是python中的一个关键字,用于在函数中生成一个生成器(Generator)
- 一个包含yield的函数不是普通函数,而是一个生成器函数
- 调用生成器函数时,返回的是一个生成器对象,而不是一个函数执行的内容
 2)作用:
- 暂停函数执行:每次遇到yield,函数会将当前状态(变量、执行位置)保存,并暂停执行
- 返回值:yield会将值返回给调用方
- 恢复执行:下次从暂停的函数继续运行,而不是从头开始
 3) yield使用示例:
def study():
    print("hello,python")
    yield 1
    print("hello,java")
    yield 2
    
gen = study()
print(next(gen))
print(next(gen))

image.png
’
4)生成器和协程
- 生成器是协程的基础:
- yield 可以暂停函数的执行。
- 使用 send() 方法可以向协程发送数据。
 
- 协程与生成器的区别:
- 生成器通过 yield 生成值。
- 协程通过 yield 接收和发送数据。
 
5)yield实现的简单协程
def simple_coroutine():
    print("create coroutine")
    # 等待接收外部发送的数据
    x = yield
    print(f"-> Coroutine received: {x}")
# 创建协程对象
coroutine = simple_coroutine()
# 启动协程
next(coroutine)
# 发送数据到协程
coroutine.send("hello")

image.png
2、greenlet
from greenlet import greenlet
def speak():
    print("我要说。。。")
    g1.switch()
    print("周末去哪里玩呢")
    g1.switch()
def study():
    print("我要学习")
    g2.switch()
    print("学习编程")
    g2.switch()
g1 = greenlet(study)
g2 = greenlet(speak)
g1.switch() #启动g1

image.png
3、gevent
gevent是一个基于协程的 Python 异步编程库,专注于简化并发任务的处理。它通过猴子补丁(monkey patching)将标准库中的阻塞调用替换为非阻塞版本,使程序可以并发执行 I/O 操作。
- Gevent 是基于 Greenlet 实现的一个更高级的协程库,支持异步网络通信。
- 使用 Gevent 可以自动处理任务调度,不需要手动调用 switch()。
1)基本函数介绍:
- spawn(func,kwargs):创建协程对象
- join():直到这个协程执行完毕
- joinall()
- sleep(): 简洁的 API 模拟阻塞行为
 2) 示例:
import gevent
data = []
def write():
    for i in range(5):
        data.append(i)
        print("添加数据:",i)
        gevent.sleep(1)
        print("添加后:",data)
def read():
    for i in range(5):
        print("read:",data[i])
        gevent.sleep(1)
        print("===")
t1 = gevent.spawn(write)
t2 = gevent.spawn(read)
# 等待所有协程完成
gevent.joinall([t1, t2])

image.png
3)、猴子补丁(Monkey Patching)
Gevent 提供了 monkey.patch_all() 方法,自动将标准库中的阻塞函数(如 time.sleep、socket)替换为非阻塞版本。
import gevent
from gevent import monkey
import  time
data = []
def write():
    for i in range(5):
        data.append(i)
        print("添加数据:",i)
        time.sleep(1)
        print("添加后:",data)
    print("函数写入完毕")
def read():
    for i in range(5):
        print("read:",data[i])
        time.sleep(1)
        print("===")
    print("函数读取完毕")
t1 = gevent.spawn(write)
t2 = gevent.spawn(read)
# 打开猴子补丁
# 注意这个位置要在join之前打开
# monkey.patch_all()
# 等待所有协程完成
gevent.joinall([t1, t2])

image.png
不开猴子补丁时,gevent并不认识time这种阻塞的函数,就当作普通函数来处理,按顺序执行
import gevent
from gevent import monkey
import  time
data = []
def write():
    for i in range(5):
        data.append(i)
        print("添加数据:",i)
        time.sleep(1)
        print("添加后:",data)
    print("函数写入完毕")
def read():
    for i in range(5):
        print("read:",data[i])
        time.sleep(1)
        print("===")
    print("函数读取完毕")
t1 = gevent.spawn(write)
t2 = gevent.spawn(read)
# 打开猴子补丁
# 注意这个位置要在join之前打开
monkey.patch_all()
# 等待所有协程完成
gevent.joinall([t1, t2])

image.png

image.png
实现了边写边读,自动切换
4)Gevent与I/0操作,并发请求多个URL
import gevent
from gevent import monkey
monkey.patch_all()  # 打开猴子补丁
import requests
urls = [
    "https://www.google.com",
    "https://www.python.org",
    "https://www.github.com"
]
def fetch_url(url):
    print(f"Fetching: {url}")
    response = requests.get(url)
    print(f"{url}: {len(response.text)} bytes received")
gevent.joinall([
    gevent.spawn(fetch_url, url) for url in urls
])

image.png
三、进程、线程、协程比对

image.png

c19ac6dbffa202bc3d68da46efb40da.png
四、实际应用场景

image.png