协程理论
一、单线程下的并发
- 本节的主题是基于单线程来实现并发
- 即只用一个主线程(很明显可利用的CPU只有一个)情况下实现并发
- 为此我们需要先回顾下并发的本质:
- 切换+保存状态
- 当CPU正在运行一个任务会在两种情况下切走去执行其他的任务
- 该任务发生了阻塞
- 该任务计算的时间过长或有一个优先级更高的程序替代了它。
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,
yield
、greenlet
都无法实现,就用到了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
"""
标签:协程,单线程,理论,线程,切换,time,print
From: https://www.cnblogs.com/taoyuanshi/p/18124739