Skip to content

Commit efe5d20

Browse files
committed
协程相关的更新
1 parent a812bf6 commit efe5d20

File tree

8 files changed

+166
-29
lines changed

8 files changed

+166
-29
lines changed

04 多进程,多线程,协程/09 asyncio协程.txt

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
协程 Coroutine
22
协程或又叫微线程, 纤程, 协程本质是在一个线程内, 通过轮询的方式执行不同的任务.
3+
对比于进程与线程是系统CPU的能力, 协程是由程序员通过代码实现的, 其建立在系统的线程之上.
34

45
工作原理
5-
对比于进程与线程是系统CPU的能力, 协程是由程序员通过代码实现的, 其建立在系统的线程之上.
66
通过应用层记录程序的上下文栈区, 实现程序运行中的跳跃. 进而实现选择代码段执行.
77

8-
一般一个线程栈大小为1MB, 使用多线程, 在高并发下, cpu大部分的时间都将用于切换线程上下文.
9-
由于线程的切换是在内核完成的, 会耗费额外的空间和时间. 而且由于内存都分配给线程栈了, 将频繁地进行内存置换算法, 浪费了很多cpu时间片.
10-
11-
协程, 可以理解为一种在线程里跑的子线程, 它的默认栈空间很小 (比如go的协程栈默认大小为2KB).
12-
当多个协程在一个线程上运行时, 协程间会切换着运行, 协程的切换完全在用户态完成, 而且时机由程序员来自行调度, 从而使得线程的并发量大大提升.
8+
协程的资源消耗
9+
一般一个线程栈大小为1MB, 使用多线程, 在高并发下, cpu大部分的时间都将用于切换线程上下文.
10+
由于线程的切换是在内核完成的, 会耗费额外的空间和时间. 而且由于内存都分配给线程栈了, 将频繁地进行内存置换算法, 浪费了很多cpu时间片.
1311

14-
但要注意的是协程只适用于IO密集型程序(大部分时间在等待的), 对于计算密集型程序, 协程的优势并不大, 因为没有给它切换的时间, cpu大部分时间都在工作.
12+
对比协程, 可以理解为一种在线程里跑的子线程, 它的默认栈空间很小 (比如go的协程栈默认大小为2KB).
13+
当多个协程在一个线程上运行时, 协程间会切换着运行, 协程的切换完全在用户态完成, 而且时机由程序员来自行调度, 从而使得线程的并发量大大提升.
1514

16-
常见的IO密集型程序有 网络请求、文件读写、数据库查询等,
15+
但要注意的是协程只适用于IO密集型程序(大部分时间在等待的).
16+
对于计算密集型程序, 协程的优势并不大, 因为没有给它切换的时间, cpu大部分时间都在工作.
17+
常见的IO密集型程序有 网络请求、文件读写、数据库查询等,
1718

1819
优点:
1920
无需多线程切换的开销
@@ -131,15 +132,42 @@ asyncio 标准库
131132
async 关键字: async定义一个协程.
132133
格式: async def 函数名(参数): pass
133134

134-
await 关键字: 遇到阻塞后,先挂起当前协程(任务),让事件循环去执行其他任务(如果有的话),等待“可等待对象”执行完成后,再继续执行下面的代码。
135-
格式: await 可等待对象
136-
获取可等待对象的返回值
137-
格式: 变量名 = await 可等待对象
135+
await 关键字: 获取可等待对象的返回值, 若协程函数有返回值, 只有拿到返回值才会解除阻塞
136+
遇到阻塞后,先挂起当前协程(任务),让事件循环去执行其他任务(如果有的话),等待“可等待对象”执行完成后,再继续执行下面的代码。
137+
格式: await 可等待对象
138+
格式: 变量名 = await 可等待对象
139+
注意
140+
await是用来等待任务返回结果的, 不是用来进行异步任务并发的
138141

139142
可等待对象
140143
可等待对象是指可以在await语句中使用的对象,它主要有三种:协程对象、Task对象和 Future对象
141144

142-
2) 事件循环
145+
2) 协程对象
146+
coroutine对象
147+
148+
以async def 函数名(): pass 的格式定义的函数, 为协程函数, 当执行一个协程函数, 会返回一个协程对象.
149+
150+
3) Task对象
151+
Task对象用于向事件循环中加入任务.
152+
153+
Task用于开发调度协程, 通过asyncio.create_task(协程对象)创建(该函数于Python3.7中添加).
154+
也可以使用asyncio.ensure_future(协程对象)创建一个Task对象.
155+
156+
asyncio.run_until_complete的参数是一个future对象, 当传入一个协程, 其内部会自动封装成Task.
157+
158+
在协程嵌套中
159+
ret1 = await fun1()
160+
ret2 = await fun2()
161+
await仅会将fun1提交到事件循环中, fun2的await并未执行
162+
163+
通过将协程对象变为Task对象, 使await不阻塞
164+
task1 = asyncio.create_task(fun1())
165+
task2 = asyncio.create_task(fun2())
166+
167+
ret1 = await task1
168+
ret2 = await tssk2
169+
Task对象不是阻塞的, await将会把这两个Task对象同时提交到任务循环中
170+
143171

144172
2) 运行协程的三种主要方式
145173
(1) 用asyncio.run()函数用来运行.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import time
2+
3+
def fun_a():
4+
while True:
5+
print(f"{fun_a.__name__}调用")
6+
yield
7+
time.sleep(1)
8+
9+
def fun_b(gen):
10+
while True:
11+
print(f"{fun_b.__name__}调用")
12+
gen.__next__() # next(gen)
13+
14+
# a 为fun_a函数的生成器, 将其给fun_b函数
15+
# fun_b函数将先执行, 打印fun_b调用, 然后获取生成器中的一个对象, 该对象调用, 将打印fun_a调用
16+
# 以上, 将会不断的交替执行, 这就是任务的切换
17+
a = fun_a()
18+
fun_b(a)

04 多进程,多线程,协程/py_process/09 协程/4_协程的使用.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,40 +14,41 @@ async def fun_work2(val):
1414

1515

1616
# 方式1
17-
# 使用另一个协程函数将要执行的协程闭包
17+
# 1) 使用另一个协程函数将要执行的协程闭包
1818
async def run_coro():
1919
await fun_work1(1) # 等待协程对象
2020
await fun_work2(2)
2121

22-
# 使用事件循环执行协程对象
22+
# 2) 使用事件循环执行协程对象
2323
# 获取事件循环
2424
loop = asyncio.get_event_loop()
25-
# 通过事件循环执行轮询事件, 直到所有的协程对象都完成
25+
# 3) 通过事件循环执行轮询事件, 直到所有的协程对象都完成
2626
loop.run_until_complete(run_coro()) # 运行协程函数将会获得一个协程对象
2727
# 关闭事件循环
2828
loop.close()
2929

3030

3131
# 方式2
32-
# 使用另一个协程函数将要执行的协程闭包
32+
# 1) 使用另一个协程函数将要执行的协程闭包
3333
async def do_coro2():
3434
tasks = [
3535
asyncio.create_task(fun_work1(11)), # 将协程对象变成Task对象
3636
asyncio.create_task(fun_work2(12))
3737
]
3838
await asyncio.wait(tasks) # 异步等待, 等待所有的任务对象完成
3939

40-
# 使用run函数省略事件循环, 直接执行 协程对象, Task对象 或 Future对象
40+
# 2) 使用run函数省略事件循环, 直接执行 协程对象, Task对象 或 Future对象
4141
asyncio.run(do_coro2())
4242

4343

4444
# 方式3
45-
# 将要执行的协程写入一个列表
45+
# 1) 将要执行的协程对象写入一个列表
4646
tasks = [
4747
fun_work1(101),
4848
fun_work2(102),
4949
]
5050

51-
# 使用run函数省略事件循环, 直接执行 协程对象, Task对象 或 Future对象
51+
# 2) 使用run函数省略事件循环, 直接执行 协程对象, Task对象 或 Future对象
52+
# 用asynico.wait函数等待协程完成
5253
asyncio.run(asyncio.wait(tasks)) # 等待所有协程对象完成
5354

04 多进程,多线程,协程/py_process/09 协程/5_await关键字.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ async def work():
88

99
# 调用协程的闭包
1010
async def do_coro():
11-
ret = await work() # 等待协程对象, 并获取协程的返回值
11+
ret = await work() # 等待协程对象, 并获取协程的返回值, 如果当前任务是一个耗时任务则将会阻塞
1212
print("获得协程的返回值:", ret)
1313

1414

04 多进程,多线程,协程/py_process/09 协程/6_协程的嵌套.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,30 @@
44
# 协程的嵌套就是用一个协程函数包含其他协程函数, 可以理解为一个闭包
55

66

7-
async def work():
8-
print(f"协程{work.__name__}启动...")
9-
await asyncio.sleep(2)
10-
print(f"协程{work.__name__}完成...")
11-
return f"返回值{work.__hash__()}"
7+
WORKTIME = 4
8+
9+
10+
async def work(id):
11+
print(f"协程{work.__name__}{id}启动...")
12+
await asyncio.sleep(WORKTIME)
13+
print(f"协程{work.__name__}{id}完成...")
14+
return f"返回值哈希值{work.__hash__()}"
1215

1316

1417
async def main(*args):
18+
count = 1
1519
for fun in args:
16-
print("将执行协程函数:", fun.__name__)
17-
ret = await fun()
20+
print(f"将执行协程函数{count}:", fun.__name__)
21+
ret = await fun(count) # 等待协程对象
1822
print("当前的返回值为:", ret)
23+
count += 1
1924

2025

2126
if __name__ == "__main__":
2227

23-
asyncio.run(main((work(), work())))
28+
asyncio.run(main(work, work))
29+
30+
2431

32+
# 以上程序忽略了await在处理有返回值函数的时候, 会一直阻塞直到拿到函数的返回值
33+
# 所以以上程序实际上是一个同步任务, 而不是异步的
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import asyncio
2+
3+
4+
# 来自于文件 6_协程的嵌套.py 中的代码
5+
# 其中因为执行await便将协程对象提交给事件循环, 并一直等待其返回值, 所以, 原代码是顺序执行的
6+
# 现在使用Task对象, 将任务变为异步执行
7+
8+
WORKTIME = 4
9+
10+
11+
async def work(id):
12+
print(f"协程{work.__name__}{id}启动...")
13+
await asyncio.sleep(WORKTIME)
14+
print(f"协程{work.__name__}{id}完成...")
15+
return f"返回值哈希值{work.__hash__()}"
16+
17+
18+
async def main(*args):
19+
task1 = asyncio.create_task(args[0](1))
20+
task2 = asyncio.create_task(args[1](2))
21+
22+
ret1 = await task1 # task不是耗时任务, 将不会阻塞等待返回值
23+
ret2 = await task2
24+
25+
print(ret1)
26+
print(ret2)
27+
28+
29+
if __name__ == "__main__":
30+
31+
asyncio.run(main(work, work))
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import asyncio
2+
3+
4+
async def coro_work(val):
5+
if not isinstance(val, int):
6+
raise ValueError("所给参数不为整数.")
7+
print(f"{coro_work.__name__}{val}将执行: {val}s.")
8+
await asyncio.sleep(val)
9+
return "已经执行{}s".format(val)
10+
11+
12+
# 方式1
13+
# 多协程并发, 使用await asyncio.wait(协程对象列表)的方式
14+
async def main1():
15+
# 创建10个任务并将10个任务提交到事件循环
16+
tasks = [asyncio.create_task(coro_work(i)) for i in range(1, 11)]
17+
18+
# asyncio.wait返回两个值, 第一个为Task对象的返回, 第二个为Task对象的执行的状态
19+
# asyncio.wait返回的任务返回值是没有顺序的
20+
# 使用await asyncio.wait将不会触发await等待返回值而阻塞程序
21+
done, pending = await asyncio.wait(tasks)
22+
23+
# print("任务执行的状态:\n", pending)
24+
# print("任务执行完毕后:\n", done)
25+
26+
# 获取任务执行的返回结果
27+
print("获得执行的结果.")
28+
for ret in done:
29+
print(ret.result())
30+
31+
32+
# 方式2
33+
# 多协程并发, 使用await asyncio.gather(协程对象1, 协程对象2, ...)的方式
34+
async def main2():
35+
# 创建10个任务并将10个任务提交到事件循环
36+
tasks = [asyncio.create_task(coro_work(i)) for i in range(1, 11)]
37+
38+
# asyncio.gather用来收集所有已完成的任务的返回值, 并且获取到的任务的返回值是有顺序的
39+
# asyncio.gather返回所有协程对象返回结果的一个列表
40+
rets = await asyncio.gather(*tasks)
41+
42+
# print("任务执行完毕后:\n", ret)
43+
44+
# 获取任务执行的返回结果
45+
print("获得执行的结果.")
46+
for ret in rets:
47+
print(ret)
48+
49+
50+
asyncio.run(main2())
Binary file not shown.

0 commit comments

Comments
 (0)