1、同步、异步
同步:是指代码调用IO操作时,必须等待IO操作完成才返回的调用方式
异步:是指代码调用IO操作时,不必等IO操作完成就返回的调用方式
阻塞、非阻塞
阻塞:从调用者的角度出发,如果在调用的时候,被卡住,不能再继续向下运行,需要等待,就说是阻塞;
非阻塞:从调用者的角度出发, 如果在调用的时候,没有被卡住,能够继续向下运行,无需等待,就说是非阻塞;
生成器-send方法
send方法有一个参数,该参数指定的是上一次被挂起的yield语句的返回值
"""斐波那契数列"""
def create_num(num):
a, b = 0, 1
current_num = 0
while current_num < num:
yield a
a, b = b, a+b
current_num += 1
return "abc"
g = create_num(5)
print(g)
# for i in g: # 循环输出
# print(i)
# print(next(g)) # 逐个取值
# print(next(g))
# print(next(g))
# print(next(g))
# print(next(g))
while True:
try:
ret = next(g)
print(ret)
except Exception as e:
# print(e, '打印出')
print(e.value, '打印出') # return 的值输出:abc 打印出
break
send方法
"""斐波那契数列"""
def create_num(num):
a, b = 0, 1
current_num = 0
while current_num < num:
result = yield a
print("输出yield a的赋值result:", result)
a, b = b, a+b
current_num += 1
return "abc"
g = create_num(5)
print(next(g)) # 如果需要使用send启动生成器,也可以先用next启动
# print(g.send(None))
print(g.send('123')) # 如果需要使用send启动生成器,第一次必须要传入None才行
print(g.send('999'))
输出结果及原因如下图所示:(重要)
当不给yield a赋值输出的时候,send方法和next功能一样,而且send传参也没有效果
"""斐波那契数列"""
def create_num(num):
a, b = 0, 1
current_num = 0
while current_num < num:
# result = yield a
yield a # 当不给yield a赋值输出的时候,send方法和next功能一样,而且send传参也没有效果
# print("输出yield a的赋值result:", result)
a, b = b, a+b
current_num += 1
return "abc"
g = create_num(5)
print(next(g)) # 如果需要使用send启动生成器,也可以先用next启动
# print(g.send(None))
print(g.send('123')) # 如果需要使用send启动生成器,第一次必须要传入None才行
print(g.send('999'))
关闭生成器
# 关闭生成器
g.close()
2、使用yield完成多任务
import time
def task1():
while True:
print("---1----")
time.sleep(0.1)
yield
def task2():
while True:
print("---2----")
time.sleep(0.1)
yield
def main():
t1 = task1()
t2 = task2()
while True:
next(t1)
next(t2)
if __name__ == '__main__':
main()
3、yield from介绍
python3.3新加了yield from语法
from itertools import chain
l = [1, 2, 3]
dic = {
"name": "asd",
"age": 18
}
for value in zip(l, dic):
print(value)
"""输出为
(1, 'name')
(2, 'age')
"""
for value in chain(l, dic, range(5, 10)):
print(value)
"""输出如下
1
2
3
name
age
5
6
7
8
9
"""
print(chain(l, dic, range(5, 10))) # <itertools.chain object at 0x000001F0A7B0AF28>
print(list(chain(l, dic, range(5, 10)))) # [1, 2, 3, 'name', 'age', 5, 6, 7, 8, 9]
def my_chain(*args, **kwargs): # *args, **kwargs代表不定长参数
for my_iterable in args:
# for value in my_iterable:
# yield value
yield from my_iterable # yield from相当于上面两句,相当于一个for循环
for value in my_chain(l, dic, range(5, 10)):
print(value)
"""输出如下
1
2
3
name
age
5
6
7
8
9
"""
def generator_1(l):
yield l
def generator_2(l):
yield from l
for i in generator_1(l):
print(i) # [1, 2, 3] 因为传参的是列表
for i in generator_2(l):
print(i)
"""
1
2
3
"""
yield from建立起调用方和子生成器的通道,子生成器完成具体任务
def generator_1(): # 【子生成器】:yield from后的generator_1()生成器函数是子生成器
total = 0
while True:
x = yield
print('加', x)
if not x: # 如果传参None,循环退出
break
total += x
return total
def generator_2(): #【委托生成器】:generator_2()是程序中的委托生成器,它负责委托子生成器完成具体任务。
while True:
total = yield from generator_1() # yield from建立起调用方和子生成器的通道
print('加和总数是:', total)
def main(): # 调用方 【调用方】:main()是程序中的调用方,负责调用委托生成器。
# g1 = generator_1()
# g1.send(None)
# g1.send(2)
# g1.send(3)
# g1.send(None)
g2 = generator_2()
g2.send(None)
g2.send(2)
g2.send(3)
g2.send(None)
if __name__ == '__main__':
main()
4、协程
协程,又称微线程
协程是python个中另外一种实现多任务的方式,只不过比线程更小占用更小执行单元(理解为需要的资源)
Python中的协程大概经历了如下三个阶段:
1、最初的生成器变形yield/send
2、yield from
3、在最近的Python3.5版本中引入async/await关键字
使用greenlet完成多任务
安装模块:pip3 install greenlet
from greenlet import greenlet
import time
# 协程利用程序IO中的延迟,或者运行IO所需的时间,来切换任务 (这里代码中的延迟0.5s代表程序IO过程中所需要的时间)
def demo1():
while True:
print("demo1")
gr2.switch() # 在延迟0.5s之间,切换到demo2的任务
time.sleep(0.5)
def demo2():
while True:
print("demo2")
gr1.switch() # 在延迟0.5s之间,切换到demo1的任务 互相切换
time.sleep(0.5)
gr1 = greenlet(demo1)
# print(greenlet.__doc__) # 打印类的说明文档
gr2 = greenlet(demo2)
gr1.switch() # 先执行demo1的任务,进行一个任务
使用gevent完成多任务
安装模块:pip3 install gevent
import gevent
import time
from gevent import monkey # monkey补丁
#将程序中的耗时操作,换成gevent模块能够识别的形式
monkey.patch_all()
def f1(n):
for i in range(n):
print(gevent.getcurrent(), i)
time.sleep(0.5)
# gevent.sleep(0.5)
def f2(n):
for i in range(n):
print(gevent.getcurrent(), i)
time.sleep(0.5) # time模块在gevent中无法识别,需要换成gevent的时间模块或者打补丁monkey.patch_all()
# gevent.sleep(0.5)
def f3(n):
for i in range(n):
print(gevent.getcurrent(), i)
time.sleep(0.5)
# gevent.sleep(0.5)
g1 = gevent.spawn(f1, 5)
# time.sleep(1) # time模块在gevent中识别不了,需要替换
gevent.sleep(1)
g2 = gevent.spawn(f2, 5)
g3 = gevent.spawn(f3, 5)
g1.join() # 这样是顺序执行
g2.join()
g3.join()
"""
<Greenlet at 0x17621773e48: f1(5)> 0
<Greenlet at 0x17621773e48: f1(5)> 1
<Greenlet at 0x1762196b048: f2(5)> 0
<Greenlet at 0x1762196b148: f3(5)> 0
<Greenlet at 0x17621773e48: f1(5)> 2
<Greenlet at 0x1762196b048: f2(5)> 1
<Greenlet at 0x1762196b148: f3(5)> 1
<Greenlet at 0x17621773e48: f1(5)> 3
<Greenlet at 0x1762196b048: f2(5)> 2
<Greenlet at 0x1762196b148: f3(5)> 2
<Greenlet at 0x17621773e48: f1(5)> 4
<Greenlet at 0x1762196b048: f2(5)> 3
<Greenlet at 0x1762196b148: f3(5)> 3
<Greenlet at 0x1762196b048: f2(5)> 4
<Greenlet at 0x1762196b148: f3(5)> 4
"""
gevent模块实现简单下载器案例
import gevent
from gevent import monkey # 补丁
monkey.patch_all()
import requests # urllib 模块进行封装后的模块 需要将requests模块放在补丁模块下面,就不会出现下面的警告,相当于补丁补入requests模块中
"""
MonkeyPatchWarning: Monkey-patching ssl after ssl has already been imported may lead to errors, including RecursionError on Python 3.6. It may also silently lead to incorrect behaviour on Python 3.7. Please monkey-patch earlier. See https://github.com/gevent/gevent/issues/1016. Modules that had direct imports (NOT patched): ['urllib3.contrib.pyopenssl (G:\\python3.6.5\\lib\\site-packages\\urllib3\\contrib\\pyopenssl.py)', 'urllib3.util (G:\\python3.6.5\\lib\\site-packages\\urllib3\\util\\__init__.py)'].
monkey.patch_all()
"""
def download(url):
print("get: %s" % url)
res = requests.get(url)
data = res.text # 网址信息
print(len(data), url)
# g1 = gevent.spawn(download, "https://www.baidu.com/")
# g2 = gevent.spawn(download, "https://www.meijutt.tv/")
# g3 = gevent.spawn(download, "https://www.kancloud.cn/ju7ran/gaoji/1474878")
# g4 = gevent.spawn(download, "https://www.csdn.net/")
# g1.join()
# g2.join()
# g3.join()
# g4.join()
gevent.joinall([ # joinall函数完成上面的效果
gevent.spawn(download, "https://www.baidu.com/"),
gevent.spawn(download, "https://www.meijutt.tv/"),
gevent.spawn(download, "https://www.kancloud.cn/ju7ran/gaoji/1474878"),
gevent.spawn(download, "https://www.csdn.net/")
])
5、进程、线程、协程对比
简单总结
- 进程是资源分配的单位
- 线程是操作系统调度的单位
- 进程切换需要的资源很最大,效率很低
- 线程切换需要的资源一般,效率一般(当然了在不考虑GIL的情况下)
- 协程切换任务资源很小,效率高
- 多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中,所以是并发
进程可以理解为打开一个QQ进行联网,多线程代表这个QQ中的不同聊天窗口,协程代表如果某一个聊天窗口中一份传输信息内容比较大,可以先处理这个聊天窗口中的信息内容小的部分,合理优化资源。