协程理论

协程理论

一、单线程下的并发

  • 本节的主题是基于单线程来实现并发
    • 即只用一个主线程(很明显可利用的CPU只有一个)情况下实现并发
    • 为此我们需要先回顾下并发的本质:
      • 切换+保存状态
  • 当CPU正在运行一个任务会在两种情况下切走去执行其他的任务
    1. 该任务发生了阻塞
    2. 该任务计算的时间过长或有一个优先级更高的程序替代了它。

PS:第二种情况其实并不能提高效率,只是为了将优先级更高的任务先执行了罢了

​ 如果切换的几个任务都是纯计算类型的话,更会因为切换所增加的时间使得效率更低

[1]yield关键字

  • 基于yield来验证

    • yield本身就是一种在单线程下可以保存任务运行状态的方法
    • yield的使用方法:
    1 yield可以保存状态
    	yield的状态保存与操作系统的保存线程状态很像,但是yield是代码级别控制的,更轻量级
    2 send可以把一个函数的结果传给另外一个函数
    	以此实现单线程内程序之间的切换
    

(1)串行执行

import time


def func1():
    for i in range(10000000):
        i + 1


def func2():
    for i in range(10000000):
        i + 1


start = time.time()
func1()
func2()
stop = time.time()
print(stop - start)

# 1.5161874294281006

(2)使用yeild实现并发

import time


def func1():
    while True:
        yield


def func2():
    g = func1()
    for i in range(10000000):
        i + 1
        next(g)


start = time.time()
func2()
stop = time.time()
print(stop - start)

# 1.8489949703216553
  • 频繁的切换会使效率更低
  • 对于单线程下,我们不可避免程序中出现IO操作,但如果我们能在自己的程序中(即用户程序级别,而非操作系统级别)控制单线程下的多个任务能在一个任务遇到IO阻塞时就切换到另外一个任务去计算,这样就保证了该线程能够最大限度地处于就绪态,即随时都可以被CPU执行的状态,相当于我们在用户程序级别将自己的IO操作最大限度地隐藏起来,从而可以迷惑操作系统,让其看到:该线程好像是一直在计算,IO比较少,从而更多的将CPU的执行权限分配给我们的线程。

二、协程介绍

[1]什么是协程

  • 是单线程下的并发,又称微线程,纤程

  • 一句话说明什么是线程:

    • 协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。
  • 需要强调的是:

    • python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出CPU执行权限,切换其他线程运行)
    • 单线程内开启协程,一旦遇到IO,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非IO操作的切换与效率无关)
  • 对比操作系统控制线程的切换,用户在单线程内控制协程的切换

[2]协程的优缺点

(1)优点

  • 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
  • 单线程内就可以实现并发的效果,最大限度地利用cpu
  • 应用程序级别速度要远远高于操作系统的切换

(2)缺点

  • 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
  • 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程(多个任务一旦有一个阻塞没有切,整个线程都阻塞在原地,该线程内的其他的任务都不能执行了)

[3]总结

  • 1.必须在只有一个单线程里实现并发
  • 2.修改共享数据不需加锁
  • 3.用户程序里自己保存多个控制流的上下文栈
  • 4.附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yieldgreenlet都无法实现,就用到了gevent模块(select机制))

三、Greenlet

  • 安装
pip3 install greenlet
  • 使用
from greenlet import greenlet


def eat(name):
    print(f' {name} eat 1')
    g2.switch('hope')
    print(f' {name} eat 2')
    g2.switch()


def play(name):
    print(f' {name} play 1')
    g1.switch()
    print(f' {name} play 2')


g1 = greenlet(eat)
g2 = greenlet(play)

g1.switch('dream')

"""
 dream eat 1
 hope play 1
 dream eat 2
 hope play 2
"""

四、Gevent

  • Gevent 是一个第三方库
  • 可以轻松通过gevent实现并发同步或异步编程
  • gevent中用到的主要模式是Greenlet
  • 它是以C扩展模块形式接入Python的轻量级协程。
  • Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。

[1]安装

pip3 install gevent

[2]使用

import gevent


def func(*args, **kwargs):
    print(args)  # (1, 2, 3)
    print(kwargs)  # {'x': 4, 'y': 5}
    return 'ok'


def func2():
    ...
    
 	
g1 = gevent.spawn(func, 1, 2, 3, x=4, y=5)

g2 = gevent.spawn(func2)

g1.join()  # 等待g1结束

g2.join()  # 等待g2结束


# 拿到func1的返回值
result = g1.value

print(result)


"""
(1, 2, 3)
{'x': 4, 'y': 5}
ok
"""
posted @ 2024-04-09 20:38  桃源氏  阅读(21)  评论(0)    收藏  举报