├── .gitignore ├── 01.协程模型回显服务器 ├── async_client.py ├── async_server.py ├── async_server.py.1 ├── async_server.py.2 ├── client.py ├── readme.md └── socket编程中setblocking()函数的使用.md ├── 02.简单协程示例 ├── multi_get.py ├── multi_get_timeout.py ├── readme.md ├── requirements.txt ├── simple_get.py └── simple_get_timeout.py ├── 03.动态添加任务 ├── asyncio_with_thread_KeyboardInterrupt.py ├── asyncio_with_thread_demo.py ├── asyncio_with_thread_production.py ├── readme.md └── requirements.txt ├── 04.tornado事件循环初试 ├── main.py ├── readme.md └── requirements.txt ├── 05.动态添加任务改进 ├── readme.md ├── task_queue.py └── task_queue_timeout.py ├── 06.协程超时-装饰器 ├── readme.md └── task_queue_timeoutit.py ├── 07.限制协程并发数量-semaphore ├── readme.md ├── task_pool.py └── task_pool_fix.py ├── 08.协程池-asyncpool └── readme.md ├── 09.协程模型回显服务器(二) ├── client.py ├── protocol_client.py ├── protocol_client_bad.py ├── protocol_server.py ├── readme.md └── server.py ├── 10.python3.7中asyncio协程回显示例 ├── client.py ├── readme.md ├── server.py └── 关于asyncio.run()的使用注意事项.md ├── 11.python3-concurrenct.futures真正的并行计算 ├── readme.md ├── step1.py ├── step2.py └── step3.py ├── 12.asyncio的call_XX函数族 ├── call.py ├── call_safe.py ├── call_safe.py.1 └── readme.md ├── 13.异步文件读写及异步数据库操作 ├── docker-compose.yml ├── insert.py ├── insert_async.py ├── insert_async.py.1 ├── readme.md ├── requirements.txt ├── writefile.py ├── writefile_async.py └── writefile_async.py.1 ├── 14.aio_http_server ├── readme.md ├── requirements.txt └── server.py ├── docs └── asyncio接口方法.md └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.pyc 3 | *.vscode 4 | .DS_Store -------------------------------------------------------------------------------- /01.协程模型回显服务器/async_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #!encoding: utf-8 3 | 4 | import socket 5 | import asyncio 6 | 7 | loop = asyncio.get_event_loop() 8 | 9 | async def echo_client(): 10 | conn_fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 11 | conn_fd.setblocking(0) 12 | await loop.sock_connect(conn_fd, ('127.0.0.1', 7777)) 13 | print('已连接到服务器...') 14 | 15 | msg = await loop.sock_recv(conn_fd, 1024) 16 | msg = msg.decode() 17 | print(msg) 18 | 19 | while True: 20 | data = input('发送: ') 21 | await loop.sock_sendall(conn_fd, data.encode()) 22 | if data == 'exit': 23 | conn_fd.close() 24 | break 25 | else: 26 | msg = await loop.sock_recv(conn_fd, 1024) 27 | msg = msg.decode() 28 | print('收到: ' + msg) 29 | 30 | loop.create_task(echo_client()) 31 | loop.run_forever() -------------------------------------------------------------------------------- /01.协程模型回显服务器/async_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #!encoding: utf-8 3 | ## python版本3.5+才有await, async 4 | 5 | import socket 6 | import asyncio 7 | 8 | loop = asyncio.get_event_loop() 9 | 10 | async def handler(conn_fd): 11 | with conn_fd: 12 | await loop.sock_sendall(conn_fd, str.encode('欢迎!')) 13 | ## 循环接受客户端发送的消息 14 | while True: 15 | data = await loop.sock_recv(conn_fd, 1024) 16 | data = data.decode() 17 | print(data) 18 | 19 | if data == 'exit': 20 | print('客户端 %s:%d 断开连接...' % (conn_fd.getpeername()[0], conn_fd.getpeername()[1])) 21 | conn_fd.close() 22 | break 23 | else: 24 | await loop.sock_sendall(conn_fd, 'Hello, '.encode() + data) 25 | 26 | async def echo_server(): 27 | listen_fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 28 | listen_fd.setblocking(0) 29 | listen_fd.bind(('0.0.0.0', 7777)) 30 | listen_fd.listen(2) 31 | print('开始监听...') 32 | 33 | while True: 34 | ## accept是一个阻塞方法, 如果没有客户端连接进入就停在这里 35 | ## addr是一个元组, 格式为 ('客户端IP', 客户端端口) 36 | conn_fd, addr = await loop.sock_accept(listen_fd) 37 | print('收到来自 %s:%d 的连接' % (conn_fd.getpeername()[0], conn_fd.getpeername()[1])) 38 | coroutine = handler(conn_fd) 39 | loop.create_task(coroutine) 40 | 41 | loop.create_task(echo_server()) 42 | loop.run_forever() -------------------------------------------------------------------------------- /01.协程模型回显服务器/async_server.py.1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #!encoding: utf-8 3 | ## python版本3.5+才有await, async 4 | 5 | import socket 6 | import asyncio 7 | 8 | loop = asyncio.get_event_loop() 9 | 10 | async def accepter(listen_fd): 11 | while True: 12 | try: 13 | conn_fd, addr = listen_fd.accept() 14 | return conn_fd, addr 15 | except BlockingIOError as e: 16 | ## print(e) 17 | continue 18 | 19 | async def reader(conn_fd): 20 | while True: 21 | try: 22 | data = conn_fd.recv(1024).decode() 23 | return data 24 | except BlockingIOError as e: 25 | ## print(e) 26 | continue 27 | 28 | async def writer(conn_fd, data): 29 | conn_fd.send(data.encode()) 30 | 31 | async def handler(conn_fd): 32 | ## await writer(conn_fd, str.encode('欢迎!')) 33 | conn_fd.send('欢迎!'.encode()) 34 | ## 循环接受客户端发送的消息 35 | while True: 36 | data = await reader(conn_fd) 37 | print(data) 38 | 39 | if data == 'exit': 40 | print('客户端 %s:%d 断开连接...' % (conn_fd.getpeername()[0], conn_fd.getpeername()[1])) 41 | conn_fd.close() 42 | break 43 | else: 44 | await writer(conn_fd, 'Hello, ' + data) 45 | 46 | async def echo_server(): 47 | listen_fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 48 | listen_fd.setblocking(0) 49 | listen_fd.bind(('0.0.0.0', 7777)) 50 | listen_fd.listen(5) 51 | print('开始监听...') 52 | 53 | while True: 54 | ## accept是一个阻塞方法, 如果没有客户端连接进入就停在这里 55 | ## addr是一个元组, 格式为 ('客户端IP', 客户端端口) 56 | conn_fd, addr = await accepter(listen_fd) 57 | print('收到来自 %s:%d 的连接' % (conn_fd.getpeername()[0], conn_fd.getpeername()[1])) 58 | loop.create_task(handler(conn_fd)) 59 | 60 | loop.create_task(echo_server()) 61 | loop.run_forever() 62 | -------------------------------------------------------------------------------- /01.协程模型回显服务器/async_server.py.2: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #!encoding: utf-8 3 | ## python版本3.5+才有await, async 4 | 5 | import socket 6 | import asyncio 7 | 8 | loop = asyncio.get_event_loop() 9 | 10 | async def reader(conn_fd): 11 | while True: 12 | try: 13 | data = conn_fd.recv(1024).decode() 14 | return data 15 | except BlockingIOError as e: 16 | ## print(e) 17 | continue 18 | 19 | async def writer(conn_fd, data): 20 | conn_fd.send(data.encode()) 21 | 22 | async def handler(conn_fd): 23 | ## await writer(conn_fd, str.encode('欢迎!')) 24 | conn_fd.send('欢迎!'.encode()) 25 | ## 循环接受客户端发送的消息 26 | while True: 27 | data = await reader(conn_fd) 28 | print(data) 29 | 30 | if data == 'exit': 31 | print('客户端 %s:%d 断开连接...' % (conn_fd.getpeername()[0], conn_fd.getpeername()[1])) 32 | conn_fd.close() 33 | break 34 | else: 35 | await writer(conn_fd, 'Hello, ' + data) 36 | 37 | async def echo_server(): 38 | listen_fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 39 | listen_fd.setblocking(0) 40 | listen_fd.bind(('0.0.0.0', 7777)) 41 | listen_fd.listen(5) 42 | print('开始监听...') 43 | 44 | while True: 45 | ## accept是一个阻塞方法, 如果没有客户端连接进入就停在这里 46 | ## addr是一个元组, 格式为 ('客户端IP', 客户端端口) 47 | conn_fd, addr = await loop.sock_accept(listen_fd) 48 | print('收到来自 %s:%d 的连接' % (conn_fd.getpeername()[0], conn_fd.getpeername()[1])) 49 | loop.create_task(handler(conn_fd)) 50 | 51 | loop.create_task(echo_server()) 52 | loop.run_forever() 53 | -------------------------------------------------------------------------------- /01.协程模型回显服务器/client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #!encoding: utf-8 3 | 4 | import socket 5 | 6 | conn_fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 7 | conn_fd.connect(('127.0.0.1', 7777)) 8 | print('已连接到服务器...') 9 | 10 | msg = conn_fd.recv(1024).decode() 11 | print(msg) 12 | 13 | while True: 14 | data = input('发送: ') 15 | conn_fd.send(data.encode()) 16 | if data == 'exit': 17 | conn_fd.close() 18 | break 19 | else: 20 | msg = conn_fd.recv(1024).decode() 21 | print('收到: ' + msg) 22 | -------------------------------------------------------------------------------- /01.协程模型回显服务器/readme.md: -------------------------------------------------------------------------------- 1 | # 协程模型回显服务器 2 | 3 | 参考文章 4 | 5 | 1. [Python黑魔法 --- 异步IO( asyncio) 协程](https://www.jianshu.com/p/b5e347b3a17c) 6 | - 参考文章1深入浅出地介绍了协程及其相关概念(loop事件循环, task任务, future结果对象), 层层递进, 容易理解. 相对于`廖雪峰`老师对`async/await`的两篇介绍文章, 更加系统, 且条理更加分明. 只不过偏了一点, 并不完全适用我们`echo server`的使用场景. 但是入门的非常棒. 7 | 2. [从 asyncio 简单实现看异步是如何工作的](https://ipfans.github.io/2016/02/simple-implement-asyncio-to-understand-how-async-works/) 8 | - 比较符合我们的项目, 尤其是对于`echo server`的协程模型, 提供了较为底层的代码, 而不是像网上大部分示例中使用`start_server`, `reader`, `writer`, 或是`asyncio.Procotol`这种更高级的工具. 9 | 3. [Low-level socket operations](https://docs.python.org/3.5/library/asyncio-eventloop.html#low-level-socket-operations) 10 | 11 | 12 | server端`async_server.py`使用了`asyncio`自带的底层封装`sock_accept`, `sock_recv`和`sock_send`, 需要python3.5+. 13 | 14 | ``` 15 | [root@efd527db107f ~]# python server.py 16 | 开始监听... 17 | 收到来自 127.0.0.1:59148 的连接 18 | get 19 | 收到来自 127.0.0.1:59150 的连接 20 | get 21 | ``` 22 | 23 | `client`没什么较大的变化, 额外又写了一个`async_client.py`客户端(...好像没什么用) 24 | 25 | > 除了python3中取消了`raw_input()`函数, byte str与普通str分成了两个不同的类型, 在`recv`与`send`前后需要调用`encode()`或`decode()`进行转换. 26 | 27 | 要使用`sock_accept`这些`asyncio`内置的异步函数, 需要设置`setblocking(False)`. 否则只有第一个客户端能与服务端进行通信, 在这个连接断开之前, 之后的客户端能与服务端建立连接但无法发送信息, 会一直阻塞. 28 | 29 | ------ 30 | 31 | `async_server.py.1`和`async_server.py.2`是我尝试不使用`asyncio`内置函数`sock_accept`这些, 而是自定义接收与发送协程的示例代码, 不过可惜运行不成功. 32 | 33 | `async_server.py.1`甚至不能与客户端建立连接, 改用`sock_accept`后, 能与第一个接入的客户端进行正常通信, 但是之后的客户端还是会被阻塞. 34 | 35 | 个人感觉异步编程精髓在于异步处理流程以及如何定义协程函数, 但是`asyncio`的封装太强, 对更深入理解协程没有太大帮助. -------------------------------------------------------------------------------- /01.协程模型回显服务器/socket编程中setblocking()函数的使用.md: -------------------------------------------------------------------------------- 1 | # socket编程中setblocking()函数的使用 2 | 3 | 参考文章 4 | 5 | 1. [“socket.error: [Errno 11] Resource temporarily unavailable” appears randomly](https://stackoverflow.com/questions/38419606/socket-error-errno-11-resource-temporarily-unavailable-appears-randomly) 6 | 7 | ```py 8 | socket.setblocking(flag) 9 | ``` 10 | 11 | 如果flag为0, 则将套接字设置为非阻塞模式. 否则, 套接字将设置为阻塞模式(默认值). 12 | 13 | 在非阻塞模式下, 如果`recv()`调用没有发现任何数据或者`send()`调用无法立即发送数据, 那么将引发`socket.error`异常. 14 | 15 | 在阻塞模式下, 这些调用在处理之前都将被阻塞. -------------------------------------------------------------------------------- /02.简单协程示例/multi_get.py: -------------------------------------------------------------------------------- 1 | #!/bin/python 2 | 3 | import asyncio 4 | import time 5 | from aiohttp import ClientSession 6 | import functools 7 | 8 | url = 'http://localhost:3000/aio' 9 | start = time.time() 10 | 11 | ## 需要接收task,如果要接收其他的参数就需要用到partial(偏函数),参数需要放到前面 12 | def callback(order, url, future): 13 | print('request {:d} to {:s} success'.format(order, url)) 14 | 15 | async def main(loop): 16 | session = ClientSession(loop = loop) 17 | ## 可以设置回调函数 18 | coroutines = [] 19 | for i in range(10): 20 | co = loop.create_task(session.get(url)) 21 | co.add_done_callback(functools.partial(callback, i, url)) 22 | coroutines.append(co) 23 | ## coroutines = [loop.create_task(session.get(url)) for i in range(10)] 24 | completed, pending = await asyncio.wait(coroutines) 25 | for c in completed: 26 | result = await c.result().read() 27 | print(result) 28 | await session.close() 29 | 30 | loop = asyncio.get_event_loop() 31 | loop.run_until_complete(main(loop)) 32 | 33 | end = time.time() 34 | print('cost %f' % (end - start)) 35 | -------------------------------------------------------------------------------- /02.简单协程示例/multi_get_timeout.py: -------------------------------------------------------------------------------- 1 | #!/bin/python 2 | 3 | import asyncio 4 | import time 5 | from aiohttp import ClientSession 6 | import functools 7 | 8 | url = 'http://localhost:3000/aio' 9 | start = time.time() 10 | 11 | ## 需要接收task,如果要接收其他的参数就需要用到partial(偏函数),参数需要放到前面 12 | def callback(order, url, future): 13 | print('request {:d} to {:s} success'.format(order, url)) 14 | 15 | async def main(loop): 16 | session = ClientSession(loop = loop) 17 | ## session.get()是异步操作, 但是只有一个任务时, 无法体现其优势 18 | ## 这里await等待mytask执行结束和直接使用urllib.urlopen()时间上没什么区别... 19 | ## 可以设置回调函数 20 | coroutines = [] 21 | for i in range(5): 22 | co = loop.create_task(session.get(url)) 23 | co.add_done_callback(functools.partial(callback, i, url)) 24 | coroutines.append(co) 25 | ## coroutines = [loop.create_task(session.get(url)) for i in range(5)] 26 | ## completed, pending都是task列表, 不过completed是已完成并且已经设置了result()的task列表. 27 | completed, pending = await asyncio.wait(coroutines, loop = loop, timeout = 10) 28 | for i in completed: 29 | result = i.result() 30 | print('task result type: ', type(result)) 31 | result = await result.read() 32 | print('result read type: ', type(result)) 33 | print('result content: ', result) 34 | ## 超时的task被放在pending列表里 35 | for i in pending: 36 | print('timeout...') 37 | print(i) 38 | print(type(i)) 39 | ## timeout的task是没有设置result()的 40 | ## print(i.result()) 41 | await session.close() 42 | 43 | loop = asyncio.get_event_loop() 44 | loop.run_until_complete(main(loop)) 45 | 46 | end = time.time() 47 | print('cost %f' % (end - start)) 48 | -------------------------------------------------------------------------------- /02.简单协程示例/readme.md: -------------------------------------------------------------------------------- 1 | # 02. 简单协程示例(包括回调及超时设置) 2 | 3 | 参考文章 4 | 5 | 1. [python asyncio add_done_callback with async def](https://stackoverflow.com/questions/44345139/python-asyncio-add-done-callback-with-async-def) 6 | 7 | ## 1. 引言 8 | 9 | python是同步语言, 许多原生方法也是按同步方式写的. 异步协程库比如`twisted`, `tornado`框架, 3.5+的`asyncio`库, 都提供了各自的异步方法. 10 | 11 | 需要说明的是, 异步操作中`yield`, `await`可以在耗时IO操作中交出CPU使用权, 但是无法与原生同步IO操作结合使用. 12 | 13 | 比如, `await urllib.urlopen(url)`是无法异步进行的, 它将仍然是阻塞的同步方法. 14 | 15 | 要想`await`一个异步的http请求, 或是异步的文件读写操作, 都必须使用异步框架/库提供的异步函数, 否则是没用的. 16 | 17 | 想想, 网上大部分教程都拿`await asyncio.sleep(n)`来模拟耗时IO操作, 这是因为`asyncio.sleep(n)`是异步方法, 对于当前协程当会交出CPU, n秒之后会切换回来继续, 但在同一个线程里的其他协程能够开始执行, 不会造成资源浪费. 如果使用`await time.sleep(n)`呢? 原生`sleep`是一个同步方法, 会一直占用CPU, 无法切换. 18 | 19 | 本章`02. 协程流程示例`中, 使用了一个`http://localhost:3000/aio`接口(由示例14实现). 对于一个请求, 它会随机沉睡`1-30`秒再返回, 返回的内容是一个json字符串, 结构为`{delay: 沉睡的秒数}`, 示例中用这个接口来学习协程的使用方法. 20 | 21 | 正如我上面所说, `await urllib.urlopen(url)`没有任何意义, 所以这些示例都将使用`aiohttp`库提供的异步函数. 22 | 23 | ## 2. 实践 24 | 25 | 回调的魅力在并发, 单个异步请求基本看不出其优势, 所以本节还给出了多个异步请求的示例. 26 | 27 | 不过这几个示例过于死板, 只能添加静态任务, 无法动态添加... 28 | 29 | 另外, asyncio提供的`add_done_callback()`绑定的回调函数只能是普通函数, 不能是`async`声明的异步函数. 参考文章1有提供异步函数作回调函数的思路, 可以参考. 30 | 31 | ### 2.1 `simple_get.py`: 单个异步请求 32 | 33 | 输出 34 | 35 | ``` 36 | task result type: 37 | result read type: 38 | result content: b'{"delay":3}' 39 | cost 3.991998 40 | ``` 41 | 42 | ### 2.2 `simple_get_timeout.py`: 单个异步请求+超时设置 43 | 44 | 单个异步请求, 使用`asyncio.wait`, 设置一个超时时间; 45 | 46 | 输出 47 | 48 | ``` 49 | task result type: 50 | result read type: 51 | result content: b'{"delay":5}' 52 | cost 6.382042 53 | ``` 54 | 55 | 上面的输出中该请求并没有超时(因为服务端的响应是随机的, 你可以多次请求, 或者调整代码中的超时时间.) 下面的示例有超时的打印 56 | 57 | ### 2.3 `multi_get.py`: 异步请求列表+回调函数 58 | 59 | 这个示例同时发起多个请求并设置了回调函数; 60 | 61 | 输出 62 | 63 | ``` 64 | request 1 to http://localhost:3000/aio success 65 | request 2 to http://localhost:3000/aio success 66 | request 4 to http://localhost:3000/aio success 67 | request 5 to http://localhost:3000/aio success 68 | request 0 to http://localhost:3000/aio success 69 | request 3 to http://localhost:3000/aio success 70 | request 9 to http://localhost:3000/aio success 71 | request 6 to http://localhost:3000/aio success 72 | request 7 to http://localhost:3000/aio success 73 | request 8 to http://localhost:3000/aio success 74 | b'{"delay":24}' 75 | b'{"delay":12}' 76 | b'{"delay":28}' 77 | b'{"delay":23}' 78 | b'{"delay":17}' 79 | b'{"delay":26}' 80 | b'{"delay":29}' 81 | b'{"delay":16}' 82 | b'{"delay":17}' 83 | b'{"delay":21}' 84 | cost 29.044463 85 | ``` 86 | 87 | 这结果很优秀, 10个请求中耗时最长的是29秒, 于是整个流程的时长也是29秒. 88 | 89 | ### 2.4 `multi_get_timeout.py`: 异步请求列表, 也设置了超时及回调函数 90 | 91 | 输出 92 | 93 | ``` 94 | request 1 to http://localhost:3000/aio success 95 | request 4 to http://localhost:3000/aio success 96 | request 2 to http://localhost:3000/aio success 97 | task result type: 98 | result read type: 99 | result content: b'{"delay":9}' 100 | task result type: 101 | result read type: 102 | result content: b'{"delay":8}' 103 | task result type: 104 | result read type: 105 | result content: b'{"delay":3}' 106 | timeout... 107 | ()> wait_for=()]> cb=[callback(3, 'http://localhost:3000/aio')() at .\multi_get_timeout.py:13]> 108 | 109 | timeout... 110 | ()> wait_for=()]> cb=[callback(0, 'http://localhost:3000/aio')() at .\multi_get_timeout.py:13]> 111 | 112 | cost 10.005833 113 | ``` 114 | 115 | 超时时间设置为10秒, 一共发起5个请求, 可以看到3个成功, 2个超时的结果. 116 | -------------------------------------------------------------------------------- /02.简单协程示例/requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp>=3.3.2 -------------------------------------------------------------------------------- /02.简单协程示例/simple_get.py: -------------------------------------------------------------------------------- 1 | #!/bin/python 2 | 3 | import asyncio 4 | import time 5 | from aiohttp import ClientSession 6 | 7 | url = 'http://localhost:3000/aio' 8 | start = time.time() 9 | 10 | async def main(loop): 11 | session = ClientSession(loop = loop) 12 | ## session.get()是异步操作, 但是只有一个任务时, 无法体现其优势 13 | ## 这里await等待mytask执行结束和直接使用urllib.urlopen()时间上没什么区别... 14 | ## mytask = loop.create_task(session.get(url)) 15 | ## await mytask 16 | ## result = mytask.result() 17 | result = await session.get(url) 18 | print('task result type: ', type(result)) 19 | result = await result.read() 20 | print('result read type: ', type(result)) 21 | print('result content: ', result) 22 | await session.close() 23 | 24 | loop = asyncio.get_event_loop() 25 | loop.run_until_complete(main(loop)) 26 | 27 | end = time.time() 28 | print('cost %f' % (end - start)) 29 | -------------------------------------------------------------------------------- /02.简单协程示例/simple_get_timeout.py: -------------------------------------------------------------------------------- 1 | #!/bin/python 2 | 3 | import asyncio 4 | import time 5 | from aiohttp import ClientSession 6 | 7 | url = 'http://localhost:3000/aio' 8 | start = time.time() 9 | 10 | async def main(loop): 11 | session = ClientSession(loop = loop) 12 | ## session.get()是异步操作, 但是只有一个任务时, 无法体现其优势 13 | ## 这里await等待mytask执行结束和直接使用urllib.urlopen()时间上没什么区别... 14 | mytask = loop.create_task(session.get(url)) 15 | ## completed, pending都是task列表, 不过completed是已完成并且已经设置了result()的task列表. 16 | completed, pending = await asyncio.wait([mytask], loop = loop, timeout = 10) 17 | for i in completed: 18 | result = i.result() 19 | print('task result type: ', type(result)) 20 | result = await result.read() 21 | print('result read type: ', type(result)) 22 | print('result content: ', result) 23 | ## 超时的task被放在pending列表里 24 | for i in pending: 25 | print(i) 26 | ## task的执行状态只有4种, pending, running, done, cancelled 27 | ## 不过await过的task, 就只有done和cancelled两种. 28 | ## 这两种都不是, 那应该就是超时了. 29 | print('done ? ', i.done()) ## False 30 | print('cancelled ? ', i.cancelled())## False 31 | print(type(i)) 32 | ## timeout的task是没有设置result()的 33 | ## print(i.result()) 34 | await session.close() 35 | 36 | loop = asyncio.get_event_loop() 37 | loop.run_until_complete(main(loop)) 38 | 39 | end = time.time() 40 | print('cost %f' % (end - start)) 41 | -------------------------------------------------------------------------------- /03.动态添加任务/asyncio_with_thread_KeyboardInterrupt.py: -------------------------------------------------------------------------------- 1 | import time 2 | import threading 3 | import asyncio 4 | import aiohttp 5 | 6 | url = 'http://localhost:3000/aio' 7 | 8 | def keep_loop(loop): 9 | asyncio.set_event_loop(loop) 10 | loop.run_forever() 11 | 12 | async def request_page(session, order): 13 | res = await session.get(url) 14 | ## resText = await res.text() 15 | resText = await res.json() 16 | print('order %d: %s' % (order, resText)) 17 | return resText 18 | 19 | try: 20 | loop = asyncio.get_event_loop() 21 | loopThread = threading.Thread(target = keep_loop, args = (loop, )) 22 | ## 子线程随主线程退出而退出 23 | ## loopThread.setDaemon(True) 24 | loopThread.start() 25 | 26 | aioSession = aiohttp.ClientSession(loop = loop) 27 | 28 | for i in range(0, 5): 29 | print('task: ', i) 30 | co = request_page(aioSession, i) 31 | ## future对象可以添加回调函数 32 | future = asyncio.run_coroutine_threadsafe(co, loop) 33 | print('主线程不会阻塞...') 34 | ## 不再使用join(timeout=timeout)方法, 毕竟实际场景中timeout的时间无法控制. 35 | ## 另外, 直接使用join()无法接受到ctrl-c信号, 这里使用while循环替代 36 | ## while True: time.sleep(1) 37 | 38 | except KeyboardInterrupt as err: 39 | print('stoping...') 40 | ## 这里要将事件循环中的任务全部手动停止, 否则在loop.close()时会出现 41 | ## Task was destroyed but it is pending! 42 | for task in asyncio.Task.all_tasks(): 43 | task.cancel() 44 | ## aioSession.close()是awaitable的协程对象 45 | asyncio.run_coroutine_threadsafe(aioSession.close(), loop) 46 | ## 事件循环的stop与close方法都是普通函数. 47 | loop.stop() 48 | ## 貌似不需要close(), 如果要调用close(), 需要在stop()后留出一点时间, 49 | ## 否则会报RuntimeError: Cannot close a running event loop 50 | ## time.sleep(1) 51 | ## loop.close() 52 | 53 | 54 | ## 输出 55 | ## order 4: {'delay': 8} 56 | ## order 2: {'delay': 16} 57 | ## order 1: {'delay': 18} 58 | ## stoping... -------------------------------------------------------------------------------- /03.动态添加任务/asyncio_with_thread_demo.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import threading 3 | import aiohttp 4 | from datetime import datetime 5 | import time 6 | 7 | url = 'http://localhost:3000/aio' 8 | 9 | start = time.time() 10 | 11 | def keep_loop(loop): 12 | loop.run_forever() 13 | 14 | async def request_page(session, order): 15 | res = await session.get(url) 16 | ## resText = await res.text() 17 | resText = await res.json() 18 | print('order %d: %s' % (order, resText)) 19 | return resText 20 | 21 | def main(): 22 | loop = asyncio.get_event_loop() 23 | loopThread = threading.Thread(target = keep_loop, args = (loop, )) 24 | loopThread.setDaemon(True) 25 | 26 | loopThread.start() 27 | aioSession = aiohttp.ClientSession(loop = loop) 28 | 29 | for i in range(0, 10): 30 | coroutine = request_page(aioSession, i) 31 | _concurrentFuture = asyncio.run_coroutine_threadsafe(coroutine, loop) 32 | 33 | loopThread.join(timeout = 40) 34 | aioSession.close() ## RuntimeWarning: coroutine 'ClientSession.close' was never awaited 35 | loop.stop() ## RuntimeError: Cannot close a running event loop 36 | loop.close() 37 | 38 | if __name__ == '__main__': 39 | main() 40 | 41 | 42 | ## order 0: {'delay': 2} 43 | ## order 2: {'delay': 3} 44 | ## order 3: {'delay': 7} 45 | ## order 1: {'delay': 7} 46 | ## order 7: {'delay': 9} 47 | ## order 5: {'delay': 10} 48 | ## order 9: {'delay': 14} 49 | ## order 6: {'delay': 14} 50 | ## order 4: {'delay': 16} 51 | ## order 8: {'delay': 29} -------------------------------------------------------------------------------- /03.动态添加任务/asyncio_with_thread_production.py: -------------------------------------------------------------------------------- 1 | import time 2 | import threading 3 | import asyncio 4 | import aiohttp 5 | 6 | url = 'http://localhost:3000/aio' 7 | 8 | def keep_loop(loop): 9 | asyncio.set_event_loop(loop) 10 | loop.run_forever() 11 | 12 | async def request_page(session, order): 13 | res = await session.get(url) 14 | ## resText = await res.text() 15 | resText = await res.json() 16 | print('order %d: %s' % (order, resText)) 17 | return resText 18 | 19 | try: 20 | loop = asyncio.get_event_loop() 21 | loopThread = threading.Thread(target = keep_loop, args = (loop, )) 22 | ## 子线程随主线程退出而退出, 如果不加这个, ctrl-c 时就算有了 try..catch.. 也会报异常. 23 | loopThread.setDaemon(True) 24 | loopThread.start() 25 | 26 | aioSession = aiohttp.ClientSession(loop = loop) 27 | 28 | for i in range(0, 5): 29 | print('task: ', i) 30 | co = request_page(aioSession, i) 31 | ## future对象可以添加回调函数 32 | future = asyncio.run_coroutine_threadsafe(co, loop) 33 | 34 | ## 不再使用join(timeout=timeout)方法, 毕竟实际场景中timeout的时间无法控制. 35 | ## 另外, 直接使用join()无法接受到ctrl-c信号, 这里使用while循环替代 36 | while True: time.sleep(1) 37 | 38 | except KeyboardInterrupt as err: 39 | print('stoping...') 40 | ## 这里要将事件循环中的任务全部手动停止, 否则在loop.close()时会出现 41 | ## Task was destroyed but it is pending! 42 | for task in asyncio.Task.all_tasks(): 43 | task.cancel() 44 | ## aioSession.close()是awaitable的协程对象 45 | asyncio.run_coroutine_threadsafe(aioSession.close(), loop) 46 | ## 事件循环的stop与close方法都是普通函数. 47 | loop.stop() 48 | ## 貌似不需要close(), 如果要调用close(), 需要在stop()后留出一点时间, 49 | ## 否则会报RuntimeError: Cannot close a running event loop 50 | ## time.sleep(1) 51 | ## loop.close() 52 | 53 | 54 | ## 输出 55 | ## order 4: {'delay': 8} 56 | ## order 2: {'delay': 16} 57 | ## order 1: {'delay': 18} 58 | ## stoping... -------------------------------------------------------------------------------- /03.动态添加任务/readme.md: -------------------------------------------------------------------------------- 1 | ## 线程 & 协程 2 | 3 | 参考文章 4 | 5 | 1. [asyncio - how to stop loop?](https://mail.python.org/pipermail/python-list/2014-June/673627.html) 6 | 7 | 2. [asyncio - how to stop loop?](https://mail.python.org/pipermail/python-list/2014-June/673646.html) 8 | 9 | 3. [asyncio - how to stop loop?](https://mail.python.org/pipermail/python-list/2014-June/673682.html) 10 | 11 | 4. [PYTHON中的协程并发](https://www.cnblogs.com/MY0213/p/8985329.html) 12 | - 示例讲解的很清楚, 各种应用场景也很全面 13 | 14 | 示例03写了动态添加协程任务的方法, 线程 + 协程配合使用, 基本思路是启动一个子线程来维护事件循环, 主线程不停创建任务丢到事件循环. 15 | 16 | - `asyncio_with_thread_demo.py`: 展示了在子线程中运行协程基本流程, 但是无法正常关闭, 包括aiohttp的ClientSession和asyncio的loop. 17 | - `asyncio_with_thread_production.py`: 修复了demo的问题, 可以作为参考. 18 | -------------------------------------------------------------------------------- /03.动态添加任务/requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp>=3.3.2 -------------------------------------------------------------------------------- /04.tornado事件循环初试/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from tornado import ioloop, queues, httpclient, gen 4 | 5 | from datetime import timedelta 6 | import time 7 | 8 | url = 'http://localhost:3000/aio' 9 | q = queues.Queue() 10 | loop = None 11 | 12 | async def fetch_url(url): 13 | print('fetching...') 14 | ## 默认请求超时时间为20s 15 | resp = await httpclient.AsyncHTTPClient().fetch(url, request_timeout = 30) 16 | html = resp.body.decode(errors = 'ignore') 17 | print(html) 18 | return html 19 | 20 | async def customer(): 21 | print('customer start...') 22 | ## 用协程代替线程做循环 23 | while True: 24 | _url = await q.get() 25 | ## html = await fetch_url(url) 26 | global loop 27 | 28 | ## loop.spawn_callback(fetch_url, url) 29 | loop.add_callback(fetch_url, url) 30 | print('customer complete...') 31 | 32 | async def producer(): 33 | print('producer start...') 34 | for _ in range(10): 35 | print('putting...') 36 | ## 异步队列, 异步存入 37 | await gen.sleep(1) 38 | await q.put(url) 39 | print('producer complete...') 40 | async def main(): 41 | ## 主协程中添加两个独立子协程 42 | ## 直接await一个带有循环的协程会阻塞, 这里直接同时启动两个 43 | ## await customer() 44 | await gen.multi([customer(), producer()]) 45 | print('等待结束...') 46 | 47 | ## export PYTHONASYNCIODEBUG=1 48 | if __name__ == '__main__': 49 | loop = ioloop.IOLoop.current() 50 | ## 主协程main 51 | loop.run_sync(main) -------------------------------------------------------------------------------- /04.tornado事件循环初试/readme.md: -------------------------------------------------------------------------------- 1 | ## 关于tornado 2 | 3 | 在最近一次看了tornado官方文档后, 发现ta并没有什么优势可言. 4 | 5 | tornado官方文档分为几个部分, 包括异步web框架, 异步http客户端与服务端, 异步socket函数工具集, 事件循环及协程定义工具集. 6 | 7 | 异步web框架可用sanic代替, 异步http客户端与服务端类似于aiohttp, 而异步socket与事件循环, 可以用asyncio代替. tornado只是这些库的集合而已, 原生库的出现让我觉得没必要使用tornado. 8 | 9 | ## 关于回调 10 | 11 | 参考文章 12 | 13 | 1. [tornado 中的异步调用模式](https://sanyuesha.com/2017/02/08/tornado-async-style/) 14 | 15 | 回调及获取返回值的示例没看懂, 不太想看了. 决定以后采用队列的形式在协程之间共享数据, 而不是用各种回调, 嵌套来完成. 16 | 17 | ## 关于示例 18 | 19 | 2018-09-17(02) 20 | 21 | 希望能有地方可以取到协程的返回值, 以便进一步处理(就像流水线一样). 查了很多文档, 但都不符合我的要求. 最终还是决定用golang相似的方式来处理协程间的数据共享. 22 | 23 | 这里发现了`add_callback()`函数...这个函数看起来像是为协程添加回调的, 但是源码中的注释为`Calls the given callback on the next I/O loop iteration.`. 其实和`spawn_callback`类似, 使用时没看到有什么不一样的地方. 24 | 25 | 2018-09-17(01) 26 | 27 | tornado的示例03参考了官方示例[Queue example - a concurrent web spider](https://www.tornadoweb.org/en/stable/guide/queues.html), 模仿了示例02, 实现了类似生产者与消费者的过程. 不过这次没有通过单独开线程去维护一个事件循环, 而是用协程 + 队列实现. 28 | 29 | 示例02是子线程维护事件循环, 主线程不停获取新任务, 然后创建协程对象丢给事件循环对象, 即, 两个线程间共享的是事件循环对象. 从这一点上看, 示例02其实是不太理想的. 需要改进. -------------------------------------------------------------------------------- /04.tornado事件循环初试/requirements.txt: -------------------------------------------------------------------------------- 1 | tornado>=5.1 -------------------------------------------------------------------------------- /05.动态添加任务改进/readme.md: -------------------------------------------------------------------------------- 1 | 示例03中我们使用了线程+协程的方式完成动态任务的添加, 但是仍然不方便, 线程间变量同步是个大问题. 2 | 3 | 之后在研究tornado时, 在ta的官方文档中得到了灵感, 就是示例04的代码. 4 | 5 | 动态添加任务不一定要开线程, 使用异步队列, 和两个循环可以模拟生产者和消费者模型. 这个示例就是对示例03的改进. 6 | 7 | ------ 8 | 9 | `task_queue_timeout.py`还为每个任务添加了超时时间的限制. 因为`run_coroutine_threadsafe()`函数不能设置超时, 所以把`fetch_url`协程用`wait_for()`函数包装了一下, 完美. 10 | 11 | `gather(coroutine1, coroutine2...])`: 同时执行多个协程函数, 每个协程算是一个参数; 12 | 13 | `wait([coroutine1, coroutine2...], timeout)`: 与`gather`一样, 同时执行多个协程函数, 不过协程函数是以列表形式传入的, 还可以设置超时时间; 14 | 15 | `wait_for(coroutine, timeout)`: 执行单个协程函数, 也可以设置超时. 16 | -------------------------------------------------------------------------------- /05.动态添加任务改进/task_queue.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import asyncio 4 | import time 5 | from aiohttp import ClientSession 6 | 7 | url = 'http://localhost:3000/aio' 8 | task_queue = asyncio.Queue() 9 | 10 | async def fetch_url(session, order, url): 11 | print('fetching...', order) 12 | res = await session.get(url) 13 | resText = await res.json() 14 | print('order %d: %s' % (order, resText)) 15 | return resText 16 | 17 | async def customer(loop): 18 | print('customer start...') 19 | aioSession = ClientSession(loop = loop) 20 | while True: 21 | _order, _url = await task_queue.get() 22 | asyncio.run_coroutine_threadsafe(fetch_url(aioSession, _order, _url), loop) 23 | 24 | print('customer complete...') 25 | 26 | async def producer(): 27 | print('producer start...') 28 | for i in range(10): 29 | await asyncio.sleep(2) 30 | await task_queue.put((i, url)) 31 | 32 | print('producer complete...') 33 | 34 | async def main(loop): 35 | co = [producer(), customer(loop)] 36 | await asyncio.wait(co) 37 | print('主线程结束...') 38 | 39 | loop = asyncio.get_event_loop() 40 | loop.run_until_complete(main(loop)) 41 | 42 | ## customer start... 43 | ## producer start... 44 | ## fetching... 0 45 | ## fetching... 1 46 | ## fetching... 2 47 | ## order 1: {'delay': 2} 48 | ## fetching... 3 49 | ## fetching... 4 50 | ## fetching... 5 51 | ## fetching... 6 52 | ## fetching... 7 53 | ## order 2: {'delay': 10} 54 | ## fetching... 8 55 | ## producer complete... 56 | ## fetching... 9 57 | ## order 0: {'delay': 18} 58 | ## order 8: {'delay': 5} 59 | ## order 9: {'delay': 3} 60 | ## order 6: {'delay': 16} 61 | ## order 5: {'delay': 20} 62 | ## order 4: {'delay': 24} 63 | ## order 3: {'delay': 27} 64 | ## order 7: {'delay': 23} -------------------------------------------------------------------------------- /05.动态添加任务改进/task_queue_timeout.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import asyncio 4 | import time 5 | from aiohttp import ClientSession 6 | 7 | url = 'http://localhost:3000/aio' 8 | task_queue = asyncio.Queue() 9 | 10 | async def fetch_url(session, order, url): 11 | print('fetching...', order) 12 | res = await session.get(url) 13 | resText = await res.json() 14 | print('order %d: %s' % (order, resText)) 15 | return resText 16 | 17 | async def fetch_url_timeout(session, order, url, loop = None, timeout = 10): 18 | try: 19 | await asyncio.wait_for(fetch_url(session, order, url), timeout) 20 | except asyncio.TimeoutError: 21 | print(f'task {order} timeout') 22 | else: 23 | print(f'task {order} complete') 24 | 25 | async def customer(loop): 26 | print('customer start...') 27 | aioSession = ClientSession(loop = loop) 28 | while True: 29 | _order, _url = await task_queue.get() 30 | asyncio.run_coroutine_threadsafe(fetch_url_timeout(aioSession, _order, _url, loop = loop, timeout = 10), loop) 31 | ## asyncio.run_coroutine_threadsafe(fetch_url(aioSession, _order, _url), loop) 32 | 33 | print('customer complete...') 34 | 35 | async def producer(): 36 | print('producer start...') 37 | for i in range(10): 38 | ## await asyncio.sleep(2) 39 | await task_queue.put((i, url)) 40 | 41 | print('producer complete...') 42 | 43 | async def main(loop): 44 | co = [producer(), customer(loop)] 45 | await asyncio.wait(co) 46 | print('主线程结束...') 47 | 48 | loop = asyncio.get_event_loop() 49 | loop.run_until_complete(main(loop)) 50 | 51 | # producer start... 52 | # producer complete... 53 | # customer start... 54 | # fetching... 0 55 | # ... 56 | # fetching... 9 57 | # order 5: {'delay': 3} 58 | # task 5 complete 59 | # order 7: {'delay': 8} 60 | # order 9: {'delay': 6} 61 | # task 7 complete 62 | # task 9 complete 63 | # task 0 timeout 64 | # ... 65 | # task 8 timeout -------------------------------------------------------------------------------- /06.协程超时-装饰器/readme.md: -------------------------------------------------------------------------------- 1 | `task_queue_timoutit.py`的内容类似于示例05的`task_queue_timeout.py`, 不过其中的超时设置抽象为了装饰器, 可重复使用. 2 | -------------------------------------------------------------------------------- /06.协程超时-装饰器/task_queue_timeoutit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import asyncio 4 | import time 5 | from aiohttp import ClientSession 6 | 7 | url = 'http://localhost:3000/aio' 8 | task_queue = asyncio.Queue() 9 | 10 | ## 带参装饰器 11 | def timeout_it(timeout = 10): 12 | def __timeout_it(func): 13 | async def warappedFunc(*args, **kwargs): 14 | try: 15 | await asyncio.wait_for(func(*args, **kwargs), timeout) 16 | except asyncio.TimeoutError: 17 | print('task timeout...') 18 | else: 19 | print('task complete...') 20 | return warappedFunc 21 | return __timeout_it 22 | 23 | @timeout_it(timeout = 20) 24 | async def fetch_url(session, order, url): 25 | print('fetching...', order) 26 | res = await session.get(url) 27 | resText = await res.json() 28 | print('order %d: %s' % (order, resText)) 29 | return resText 30 | 31 | async def customer(loop): 32 | print('customer start...') 33 | aioSession = ClientSession(loop = loop) 34 | while True: 35 | _order, _url = await task_queue.get() 36 | asyncio.run_coroutine_threadsafe(fetch_url(aioSession, _order, _url), loop) 37 | 38 | print('customer complete...') 39 | 40 | async def producer(): 41 | print('producer start...') 42 | for i in range(20): 43 | ## await asyncio.sleep(2) 44 | await task_queue.put((i, url)) 45 | 46 | print('producer complete...') 47 | 48 | async def main(loop): 49 | co = [producer(), customer(loop)] 50 | await asyncio.wait(co) 51 | print('主线程结束...') 52 | 53 | loop = asyncio.get_event_loop() 54 | loop.run_until_complete(main(loop)) 55 | 56 | # producer start... 57 | # producer complete... 58 | # customer start... 59 | # fetching... 0 60 | # ... 61 | # fetching... 19 62 | # order 1: {'delay': 3} 63 | # task complete... 64 | # order 0: {'delay': 4} 65 | # task complete... 66 | # order 17: {'delay': 6} 67 | # order 7: {'delay': 6} 68 | # task complete... 69 | # task complete... 70 | # order 19: {'delay': 14} 71 | # order 15: {'delay': 14} 72 | # task complete... 73 | # task complete... 74 | # order 14: {'delay': 15} 75 | # task complete... 76 | # order 10: {'delay': 15} 77 | # order 4: {'delay': 15} 78 | # task complete... 79 | # task complete... 80 | # order 3: {'delay': 18} 81 | # task complete... 82 | # task timeout... 83 | # ... 84 | # task timeout... -------------------------------------------------------------------------------- /07.限制协程并发数量-semaphore/readme.md: -------------------------------------------------------------------------------- 1 | 参考文章 2 | 3 | 1. [python:利用asyncio进行快速抓取](https://blog.csdn.net/jb19900111/article/details/22692231) 4 | 5 | 2. [python并发2:使用asyncio处理并发](http://blog.gusibi.com/post/python-asyncio/) 6 | 7 | 3. [爬取博客详细页面的标题(python3.5以上,async/await,aiohttp)](https://blog.csdn.net/u013055678/article/details/54172693) 8 | 9 | 4. [python 3.7 Semaphore官方文档](https://docs.python.org/3/library/asyncio-sync.html?highlight=semaphore#semaphore) 10 | 11 | 5. [asyncio提供的`add_done_callback()`绑定的回调函数只能是普通函数, 不能是`async`声明的异步函数](https://testerhome.com/articles/19703) 12 | - `asyncio.run_coroutine_threadsafe()`返回的是`concurrent.futures._base.Future`对象, 不能使用`await`, 但是可以添加回调`add_done_callback()` 13 | 6. [07.限制协程并发数量-semaphore/task_pool.py任务完成后 并没有自动退出](https://github.com/generals-space/pyasync/issues/1) 14 | 15 | ## 1. 引言 16 | 17 | 虽然协程比线程更轻量, 占用资源更少, 但也不能无限制的增加. 如果每个协程任务不只占用IO, 如果协程还消耗了较多了CPU/内存呢? 按照生产者/消费者模型, 生产者不停向队列里添加任务, 消费者不停从队列里取任务并向循环中添加, 如果没有限制, 终究会耗尽服务器资源的. 18 | 19 | 没错, 就类似线程池, 我们也需要协程池. `asyncio`提供了`semaphore`机制, 来限制同时执行的协程数量. 20 | 21 | 引用参考文章2中一段话: 22 | 23 | > `Semaphore` 对象维护着一个内部计数器, 若在对象上调用 `.acquire()` 协程方法, 计数器则递减; 若在对象上调用 `.release()` 协程方法, 计数器则递增. 计数器的值是在初始化的时候设定. 如果计数器大于0, 那么调用 `.acquire()` 方法不会阻塞, 如果计数器为0, `.acquire()` 方法会阻塞调用这个方法的协程, 直到其他协程在同一个 `Semaphore` 对象上调用 `.release()` 方法, 让计数器递增. 24 | 25 | 不过网上大部分示例都没有调用过`acquire()`和`release()`方法, 而是直接把`semaphore`对象当作上下文管理器来用. 26 | 27 | 参考文章4中展示了semaphore对象的两种不同的使用方式. 28 | 29 | ```py 30 | sem = asyncio.Semaphore(10) 31 | 32 | # ... later 33 | async with sem: 34 | # work with shared resource 35 | ``` 36 | 37 | 和 38 | 39 | ```py 40 | sem = asyncio.Semaphore(10) 41 | 42 | # ... later 43 | await sem.acquire() 44 | try: 45 | # work with shared resource 46 | finally: 47 | sem.release() 48 | ``` 49 | 50 | ## 2. 实践 51 | 52 | 在本示例中, `producer`生产者创建了50个任务, 添加到异步队列中, 然后`customer`消费者从队列中取得任务并执行. 创建的过程很快就完成了, 因为本示例只是通过协程池来限制`customer`消费者的行为, 并不是一个**生产<->消费**的双向流程. 53 | 54 | `customer`的行为表现就如同多线程一样, 只能同时执行5个任务, 但是只有等一个任务完成, 才能处理下一个. 55 | 56 | 在试验过程中, 我曾尝试在`customer`函数的`while`循环中添加`semaphore`锁, 但是无效, 然后注释掉了. 猜测`semaphore`只在事件循环内部才有效, 单纯的`await`一个协程任务是没有用的. 57 | 58 | ...那什么是事件循环内部? 59 | 60 | 我们通过`run_coroutine_threadsafe`添加协程任务到事件循环, 通过`wait`等待一堆任务在事件循环中执行完毕...类似这种, 在协程中调用`semaphore`锁才有意义. 61 | 62 | ## 3. 更新 63 | 64 | 今天收到一个issue, 见参考文章6. 65 | 66 | > 07.限制协程并发数量-semaphore/task_pool.py,任务完成后 并没有自动退出,如何自动退出? 67 | 68 | 在写这个示例的时候, 我并没有考虑让程序自动退出的功能. 因为在实际场景中, 一般任务数量不能确定, `worker`线程是不会主动退出的. 69 | 70 | 如果按照本例实验过程中, 协程数量固定为50, 可以通过`run_coroutine_threadsafe()`方法返回的`future`对象进行判断, 见[02.简单协程示例/multi_get.py]()示例. 71 | 72 | 但是示例02是所有协程同时发起, 然后统一等待结果. 在这个`Semaphore`示例中, 我们限制了并发数量为5, 总不能每5个任务作为一批, 这一批全部执行完再统一换下一批吧? 73 | 74 | 我想他问的应该是希望协程池能有像线程池的`wait()`一样的功能(比如`threadpool`的`wait()`方法), 这种场景大多是先向线程池中添加任务, 添加完成后`start()`开始, 然后等待结果, 并不是一个动态的过程. 75 | 76 | ```py 77 | from threadpool import ThreadPool, makeRequests 78 | 79 | ## 创建任务 80 | reqs = makeRequests(self.worker, self.clusterList) 81 | ## 将任务添加到线程池中 82 | for req in reqs: self.tPool.putRequest(req) 83 | logger.info('启动线程池... %d' % POOL_SIZE) 84 | self.tPool.wait() 85 | ``` 86 | 87 | 我最终也没有找到一个比较官方的方法, 只能用了一个比较 hack 的方式, 见`task_pool_fix.py`, 用**回调+结果集**两个条件一起判断最后一个协程执行完毕, 感觉...不太完美. 88 | -------------------------------------------------------------------------------- /07.限制协程并发数量-semaphore/task_pool.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import asyncio 4 | import time 5 | from aiohttp import ClientSession 6 | 7 | url = 'http://localhost:3000/aio' 8 | task_queue = asyncio.Queue() 9 | 10 | ## 为了能明显的看出semaphore的效果, 这里设置的小一些. 11 | sem = asyncio.Semaphore(5) 12 | 13 | async def fetch_url(session, order, url): 14 | async with sem: 15 | print('fetching...', order) 16 | res = await session.get(url) 17 | resText = await res.json() 18 | print('order %d: %s' % (order, resText)) 19 | return resText 20 | 21 | async def customer(loop): 22 | print('customer start...') 23 | aioSession = ClientSession(loop = loop) 24 | ## 在这里加semaphore锁是无效的...为啥? 25 | ## sem = asyncio.Semaphore(5) 26 | ## while True: 27 | ## async with sem: 28 | ## _order, _url = await task_queue.get() 29 | ## asyncio.run_coroutine_threadsafe(fetch_url(aioSession, _order, _url), loop) 30 | while True: 31 | ## _order 任务序号, 用来标识任务的先后顺序 32 | _order, _url = await task_queue.get() 33 | asyncio.run_coroutine_threadsafe(fetch_url(aioSession, _order, _url), loop) 34 | print('customer complete...') 35 | 36 | async def producer(): 37 | print('producer start...') 38 | for i in range(50): 39 | await task_queue.put((i, url)) 40 | 41 | print('producer complete...') 42 | 43 | async def main(loop): 44 | co = [producer(), customer(loop)] 45 | await asyncio.wait(co) 46 | print('主线程结束...') 47 | 48 | loop = asyncio.get_event_loop() 49 | loop.run_until_complete(main(loop)) 50 | 51 | # customer start... 52 | # producer start... 53 | # producer complete... 54 | # fetching... 0 55 | # fetching... 1 56 | # fetching... 2 57 | # fetching... 3 58 | # fetching... 4 59 | # order 3: {'delay': 3} 60 | # fetching... 5 61 | # ... 62 | # order 42: {'delay': 19} 63 | # fetching... 49 64 | # order 44: {'delay': 22} 65 | # order 49: {'delay': 11} 66 | # order 48: {'delay': 18} 67 | # order 47: {'delay': 21} 68 | # order 46: {'delay': 27} -------------------------------------------------------------------------------- /07.限制协程并发数量-semaphore/task_pool_fix.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import asyncio 4 | import time 5 | from aiohttp import ClientSession 6 | 7 | url = 'http://localhost:3000/aio' 8 | done = False 9 | task_queue = asyncio.Queue() 10 | result_set = set() 11 | 12 | ## 为了能明显的看出semaphore的效果, 这里设置的小一些. 13 | sem = asyncio.Semaphore(5) 14 | 15 | async def fetch_url(session, order, url): 16 | async with sem: 17 | print('fetching...', order) 18 | res = await session.get(url) 19 | resText = await res.json() 20 | print('order %d: %s' % (order, resText)) 21 | return resText 22 | 23 | def callback(future): 24 | ''' 25 | asyncio提供的`add_done_callback()`绑定的回调函数只能是普通函数, 26 | 不能是`async`声明的异步函数 27 | ''' 28 | result_set.remove(future) 29 | ## 如果是最后一个任务(任务队列已空, 结果集合也空的时候) 30 | if task_queue.empty() and len(result_set) == 0: 31 | global done 32 | done = True 33 | 34 | async def customer(loop): 35 | print('customer start...') 36 | aioSession = ClientSession(loop = loop) 37 | ## 在这里加semaphore锁是无效的...为啥? 38 | ## sem = asyncio.Semaphore(5) 39 | ## while True: 40 | ## async with sem: 41 | ## _order, _url = await task_queue.get() 42 | ## asyncio.run_coroutine_threadsafe(fetch_url(aioSession, _order, _url), loop) 43 | while not done: 44 | if task_queue.empty(): 45 | ## print('wait ') 46 | await asyncio.sleep(1) 47 | continue 48 | ## _order 任务序号, 用来标识任务的先后顺序 49 | _order, _url = task_queue.get_nowait() 50 | future = asyncio.run_coroutine_threadsafe(fetch_url(aioSession, _order, _url), loop) 51 | future.add_done_callback(callback) 52 | result_set.add(future) 53 | 54 | await aioSession.close() 55 | print('customer complete...') 56 | 57 | async def producer(): 58 | print('producer start...') 59 | for i in range(50): 60 | await task_queue.put((i, url)) 61 | 62 | print('producer complete...') 63 | 64 | async def main(loop): 65 | ## co = [producer(), customer(loop), result_handler(loop)] 66 | co = [producer(), customer(loop)] 67 | await asyncio.wait(co) 68 | print('主线程结束...') 69 | 70 | loop = asyncio.get_event_loop() 71 | loop.run_until_complete(main(loop)) 72 | 73 | # customer start... 74 | # producer start... 75 | # producer complete... 76 | # fetching... 0 77 | # fetching... 1 78 | # fetching... 2 79 | # fetching... 3 80 | # fetching... 4 81 | # order 3: {'delay': 3} 82 | # fetching... 5 83 | # ... 84 | # order 42: {'delay': 19} 85 | # fetching... 49 86 | # order 44: {'delay': 22} 87 | # order 49: {'delay': 11} 88 | # order 48: {'delay': 18} 89 | # order 47: {'delay': 21} 90 | # order 46: {'delay': 27} 91 | # customer complete... 92 | # 主线程结束... 93 | -------------------------------------------------------------------------------- /08.协程池-asyncpool/readme.md: -------------------------------------------------------------------------------- 1 | 2 | [CaliDog/asyncpool](https://github.com/CaliDog/asyncpool/pulls) 3 | 4 | [gistart/asyncio-pool](https://github.com/gistart/asyncio-pool) 5 | - gevent like -------------------------------------------------------------------------------- /09.协程模型回显服务器(二)/client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #!encoding:utf-8 3 | 4 | import asyncio 5 | 6 | async def echoClient(): 7 | reader, writer = await asyncio.open_connection(host = '127.0.0.1', port = 6379) 8 | print('已连接到服务器...') 9 | 10 | while True: 11 | data = input('发送: ') 12 | writer.write(data.encode()) 13 | if data == 'exit': 14 | writer.close() 15 | break 16 | else: 17 | msgRaw = await reader.read(1024) 18 | msg = msgRaw.decode().strip() 19 | print('收到: ' + msg) 20 | 21 | loop = asyncio.get_event_loop() 22 | loop.run_until_complete(echoClient()) 23 | loop.close() -------------------------------------------------------------------------------- /09.协程模型回显服务器(二)/protocol_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #!encoding:utf-8 3 | 4 | import asyncio 5 | import sys 6 | 7 | class EchoClientProtocol(asyncio.Protocol): 8 | def __init__(self, loop, q): 9 | self.loop = loop 10 | self.queue = q 11 | ## 协程锁, 在建立好与服务端的连接后解除. 12 | self._ready = asyncio.Event() 13 | self.sendingTask = asyncio.ensure_future(self.sendMsg()) 14 | 15 | async def sendMsg(self): 16 | await self._ready.wait() 17 | print('准备发送消息') 18 | while True: 19 | data = await self.queue.get() 20 | self.transport.write(data.encode()) 21 | if data == 'exit': break 22 | self.transport.close() 23 | 24 | def connection_made(self, transport): 25 | print('已连接到服务器...') 26 | self.transport = transport 27 | ## 解开协程锁 28 | self._ready.set() 29 | 30 | def data_received(self, dataInRaw): 31 | data = dataInRaw.decode().strip() 32 | print('收到: ' + data) 33 | 34 | def connection_lost(self, exc): 35 | print('服务端断开连接, 终止事件循环...') 36 | self.sendingTask.cancel() 37 | self.loop.stop() 38 | 39 | q = asyncio.Queue() 40 | loop = asyncio.get_event_loop() 41 | 42 | ## 异步读取标准输入, 将输入值添加到队列中 43 | def got_stdin_data(q): 44 | asyncio.ensure_future(q.put(sys.stdin.readline())) 45 | 46 | loop.add_reader(sys.stdin, got_stdin_data, q) 47 | 48 | ## 注意create_connection的第一个参数, 其实是一个函数. 49 | clientCoro = loop.create_connection(lambda : EchoClientProtocol(loop, q), host='127.0.0.1', port=6379) 50 | 51 | loop.run_until_complete(clientCoro) 52 | loop.run_forever() 53 | loop.close() -------------------------------------------------------------------------------- /09.协程模型回显服务器(二)/protocol_client_bad.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #!encoding:utf-8 3 | 4 | import asyncio 5 | 6 | class EchoClientProtocol(asyncio.Protocol): 7 | def __init__(self, loop): 8 | self.loop = loop 9 | 10 | def connection_made(self, transport): 11 | print('已连接到服务器...') 12 | while True: 13 | data = input('发送: ') 14 | transport.write(data.encode()) 15 | if data == 'exit': break 16 | 17 | print('发送结束, 连接断开...') 18 | transport.close() 19 | 20 | def data_received(self, dataInRaw): 21 | data = dataInRaw.decode().strip() 22 | print('收到: ' + data) 23 | 24 | def connection_lost(self, exc): 25 | print('服务端断开连接, 终止事件循环...') 26 | self.loop.stop() 27 | 28 | loop = asyncio.get_event_loop() 29 | 30 | ## 注意create_connection的第一个参数, 其实是一个函数. 31 | clientCoro = loop.create_connection(lambda : EchoClientProtocol(loop), host='127.0.0.1', port=6379) 32 | 33 | loop.run_until_complete(clientCoro) 34 | loop.run_forever() 35 | loop.close() 36 | 37 | 38 | ## 运行测试 39 | 40 | ## 已连接到服务器... 41 | ## 发送: get 42 | ## 发送: go 43 | ## 发送: exit 44 | ## 发送结束, 连接断开... 45 | ## 服务端断开连接, 终止事件循环... 46 | -------------------------------------------------------------------------------- /09.协程模型回显服务器(二)/protocol_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #!encoding:utf-8 3 | 4 | import asyncio 5 | 6 | loop = asyncio.get_event_loop() 7 | 8 | ## 可以看作是客户端连接的处理逻辑的工厂类. 9 | ## 类似于nodejs中server的on事件绑定回调. 10 | class EchoServerProtocol(asyncio.Protocol): 11 | def connection_made(self, transport): 12 | self.addr = transport.get_extra_info('peername') 13 | self.transport = transport 14 | print('客户端 %s 接入', self.addr) 15 | 16 | def data_received(self, dataInRaw): 17 | dataIn = dataInRaw.decode().strip() 18 | print("Received %s from %s" % (dataIn, self.addr)) 19 | 20 | self.transport.write(dataInRaw) 21 | if dataIn == 'exit': 22 | self.transport.close() 23 | print('客户端 %s 断开', self.addr) 24 | 25 | serverCoro = loop.create_server(EchoServerProtocol, host = '0.0.0.0', port = 6379) 26 | 27 | try: 28 | server = loop.run_until_complete(serverCoro) 29 | loop.run_forever() 30 | except KeyboardInterrupt: 31 | print('user entered Ctrl + C...') 32 | server.close() 33 | # server.wait_closed返回一个 future 34 | # 调用loop.run_until_complete 方法,运行 future 35 | loop.run_until_complete(server.wait_closed()) 36 | loop.close() # 终止事件循环 37 | -------------------------------------------------------------------------------- /09.协程模型回显服务器(二)/readme.md: -------------------------------------------------------------------------------- 1 | 参考文章 2 | 3 | 1. [python并发3:使用asyncio编写服务器](https://segmentfault.com/a/1190000010009295) 4 | - asyncio.start_server()的C/S示例 5 | 6 | 2. [Prompt for user input using python asyncio.create_server instance](https://stackoverflow.com/questions/29081929/prompt-for-user-input-using-python-asyncio-create-server-instance) 7 | 8 | 3. [Asyncio persisent client protocol class using queue](https://stackoverflow.com/questions/30937042/asyncio-persisent-client-protocol-class-using-queue/30940625#30940625) 9 | 10 | 此示例是`01.协程模型回显服务器`的进一步扩展. 11 | 12 | 在示例`01`中, 使用原生`socket`库创建socket套接字, 然后使用`loop.sock_accept()`处理连接, 使用`loop.sock_recv()`和`loop.sock_sendall()`收发数据. 13 | 14 | 我以为之后的异步socket就是要用这种方式了, 然后我发现了`loop.create_server()`和`asyncio.start_server()`这两个东西... 15 | 16 | `server.py`与`client.py`这一对还比较常规, 参考了参考文章1, 用`asyncio.start_server()`代替原生socket创建server, 也不必再像示例01中使用`sock_accept()`去等待客户端连接. `start_server()`的使用方法非常简单, 类似于web框架中的路由与处理函数的关系, 处理函数会传入reader, writer两个参数, 通过ta们进行数据的读写. 17 | 18 | 然后是`protocol_server.py`与`protocol_client.py`这一对, 使用了`loop.create_server()`方法. 其实点进`asyncio.start_server()`的源码中你会发现ta调用的正是`loop.create_server()`. 但是直接使用还是比较麻烦的. 19 | 20 | `create_server`方法需要传入一个工厂类(`asyncio.Protocol`的实例), 用作客户端连接的处理. 我们需要在其中重写各种钩子函数, 类似于nodejs中的`socket.on('connect'|'data'|'close')`这种. 本来我以为这样会很简单的, 但是在客户端程序中, 命令行异步读取用户输入的时候遇到了问题. 21 | 22 | 原生`input`方法无疑是一个阻塞的同步方法, 在`connection_made()`方法中使用`while`循环读取用户输入然后发送给服务端. 这个流程是没错的, 但是由于while + 同步input, 使得`data_received()`的处理函数无法被调用(因为while循环一直占用着CPU不会让出), 这就导致回显的`print`语句无法打印. 23 | 24 | 为了解决这个问题, 我们需要使用一个异步的标准输入的读取函数, 然后我找到了参考文章2. 与我们的情况相同, 题主也是想扩展asyncio官方文档中的回显服务器, 希望用户能自行输入信息传入服务端再回显. 而答主提供了一个asyncio中异步读取一个文件描述符的函数`got_stdin_data`, 很合用. 但是答主的例子是服务端的, 我只采用了这个函数和异步队列的想法. 25 | 26 | 之后又遇到一个问题, 用户的输入传入到队列里了, 但是Protocol的成员方法全是同步的, 非`asaync`定义的函数没法使用`await`... 27 | 28 | 为此又找到了参考文章3, 答主给出的解决方案正是针对客户端逻辑的. 其基本原理是, 将用户输入的消息队列传入给工厂类的构造函数, 然后定义一个协程任务作为工厂类的成员函数, 在与服务端建立连接后开始执行这个任务, 而在这个任务中, 就是用while循环不停从队列中取数据然后发送出去的逻辑了. 29 | 30 | good, 队列真是个好东西. 31 | 32 | ------ 33 | 34 | 总结一下, 我本以为`create_server`的工厂模型会简单一些, 但仔细想来, ta使用类似于nodejs的处理方法, 把读与写独立在两个函数中, 会显得有些割裂. 当读写操作相互依赖时, 这个模式必然会带来一些麻烦. 日后根据业务逻辑再进行选择吧. -------------------------------------------------------------------------------- /09.协程模型回显服务器(二)/server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #!encoding:utf-8 3 | 4 | import asyncio 5 | 6 | loop = asyncio.get_event_loop() 7 | 8 | ## 对每一个客户端连接都使用这个函数来处理 9 | ## 两个参数是asyncio.StreamReader 对象和 asyncio.StreamWriter 对象 10 | async def echoHandler(reader, writer): 11 | addr = writer.get_extra_info('peername') 12 | print('客户端 %s 接入', addr) 13 | while True: 14 | ## StreamReader对象有多种read方法, 这里采用最直接的. 15 | dataInRaw = await reader.read(1024) 16 | dataIn = dataInRaw.decode().strip() ## 使用strip()移除首尾空白字符 17 | ## 得到套接字的远程地址 18 | print("Received %s from %s" % (dataIn, addr)) 19 | 20 | writer.write(dataInRaw) 21 | ## 刷新writer缓冲区 22 | await writer.drain() 23 | 24 | if dataIn == 'exit': 25 | break 26 | writer.close() 27 | print('客户端 %s 断开', addr) 28 | 29 | ## 创建异步socket服务器, 对每个客户端连接都使用echoHandler处理. 30 | serverCoro = asyncio.start_server(echoHandler, host = '0.0.0.0', port = 6379) 31 | 32 | try: 33 | server = loop.run_until_complete(serverCoro) 34 | loop.run_forever() 35 | except KeyboardInterrupt: 36 | print('user entered Ctrl + C...') 37 | server.close() 38 | # server.wait_closed返回一个 future 39 | # 调用loop.run_until_complete 方法,运行 future 40 | loop.run_until_complete(server.wait_closed()) 41 | # 终止事件循环 42 | loop.close() 43 | 44 | ## 以下使用create_task() + run_forever(), 也是正确的做法 45 | ## try: 46 | ## ## 使用create_task() + run_forever()也可以达到目的. 47 | ## ## 但是create_task(协程)得到的是task对象(拥有pending, running状态的那种) 48 | ## ## 而run_untile_complete(协程)可以启动事件循环并得到futurn的result对象. 49 | ## task = loop.create_task(serverCoro) 50 | ## loop.run_forever() 51 | ## except KeyboardInterrupt: 52 | ## print('user entered Ctrl + C...') 53 | ## server = task.result() 54 | ## server.close() 55 | ## # server.wait_closed返回一个 future 56 | ## # 调用loop.run_until_complete 方法,运行 future 57 | ## loop.run_until_complete(server.wait_closed()) 58 | ## loop.close() # 终止事件循环 -------------------------------------------------------------------------------- /10.python3.7中asyncio协程回显示例/client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #!encoding:utf-8 3 | 4 | import asyncio 5 | 6 | async def echoClient(): 7 | reader, writer = await asyncio.open_connection(host = '127.0.0.1', port = 6379) 8 | print('已连接到服务器...') 9 | 10 | while True: 11 | data = input('发送: ') 12 | writer.write(data.encode()) 13 | if data == 'exit': 14 | break 15 | else: 16 | msgRaw = await reader.read(1024) 17 | msg = msgRaw.decode().strip() 18 | print('收到: ' + msg) 19 | writer.close() 20 | await writer.wait_closed() 21 | 22 | asyncio.run(echoClient()) 23 | -------------------------------------------------------------------------------- /10.python3.7中asyncio协程回显示例/readme.md: -------------------------------------------------------------------------------- 1 | 参考文章 2 | 3 | 1. [asyncio — Asynchronous I/O](https://docs.python.org/3.7/library/asyncio.html) 4 | 5 | 之前的示例是在python3.6下的, 从来没有使用过`asyncio.coroutine`装饰器和`yield from`, 直接上的`async`和`await`. 6 | 7 | 然而官网上很快就出现了3.7的链接. 这一版的其他更新就算了, `asyncio`库的可用性也有了很大的优化. 8 | 9 | 首先就是不必再手动创建或获取事件循环了, 不必在`get_event_loop`然后`run_forever`或`run_until_complete`, 直接一个`asyncio.run(协程)`就可以了, 方便好多. 10 | 11 | 然后就是官方文档的条理变得很清晰, ta将`asyncio`提供的API分为了两部分, 高级接口和低级(底层)接口. 12 | 13 | 高级接口包括: 14 | 15 | 1. 协程coroutine和任务task 16 | 2. stream流(socket相关, 不是`sock_recv`或`sock_sendall`这种, 而是`reader`和`writer`) 17 | 3. 同步锁的异步实现(`Lock`, `Event`, `Semaphore`等) 18 | 4. 异步进程(异步的exec族, popen, shell等方法和异步进程间通信) 19 | 5. 异步队列(用协程间通信十分方便) 20 | 6. 异步异常 21 | 22 | 低级接口包括: 23 | 24 | 1. 事件循环(获取循环的方法, 回调处理, 服务器对象(socket), 事件循环的实现等) 25 | 2. `Future`对象 26 | 3. `Transport`和`Procotol`工厂类 27 | 4. `Policies`事件循环对象, 没用过 28 | 5. ~~平台支持~~ 呃, 这个不算, 只是讲了下不同平台下有些接口不太通用. 29 | 30 | 在3.7之前, `get_event_loop`等相关函数是属于低级接口中的事件循环的部分, 3.7中出现了`asyncio.run`并把这个方法归为高级接口中的协程coroutine和任务task这部分了, 并且建议使用高级接口. 31 | 32 | 示例10中使用`asyncio.run()`重写了示例09中的`asyncio.start_server()`示例...为什么不重写`loop.create_server()`? 官方都没有这个的示例...写起来太麻烦了, 不写了. 33 | -------------------------------------------------------------------------------- /10.python3.7中asyncio协程回显示例/server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #!encoding:utf-8 3 | 4 | import asyncio 5 | 6 | loop = asyncio.get_event_loop() 7 | 8 | ## 对每一个客户端连接都使用这个函数来处理 9 | ## 两个参数是asyncio.StreamReader 对象和 asyncio.StreamWriter 对象 10 | async def echoHandler(reader, writer): 11 | addr = writer.get_extra_info('peername') 12 | print('客户端 %s 接入', addr) 13 | while True: 14 | ## StreamReader对象有多种read方法, 这里采用最直接的. 15 | dataInRaw = await reader.read(1024) 16 | dataIn = dataInRaw.decode().strip() ## 使用strip()移除首尾空白字符 17 | ## 得到套接字的远程地址 18 | print("Received %s from %s" % (dataIn, addr)) 19 | 20 | writer.write(dataInRaw) 21 | ## 刷新writer缓冲区 22 | await writer.drain() 23 | 24 | if dataIn == 'exit': 25 | break 26 | writer.close() 27 | print('客户端 %s 断开', addr) 28 | 29 | 30 | async def echoServer(): 31 | ## 创建异步socket服务器, 对每个客户端连接都使用echoHandler处理. 32 | serverCoro = await asyncio.start_server(echoHandler, host = '0.0.0.0', port = 6379) 33 | addr = serverCoro.sockets[0].getsockname() 34 | print('正在监听: ', addr) 35 | async with serverCoro: 36 | await serverCoro.serve_forever() 37 | 38 | try: 39 | asyncio.run(echoServer()) 40 | except KeyboardInterrupt: 41 | print('user entered Ctrl + C...') 42 | -------------------------------------------------------------------------------- /10.python3.7中asyncio协程回显示例/关于asyncio.run()的使用注意事项.md: -------------------------------------------------------------------------------- 1 | # 关于asyncio.run()的使用注意事项 2 | 3 | `asyncio.run()`是python3.7新增的高级接口, 与`loop.run_until_complete()`相似, 但是不能混用...看看示例吧. 4 | 5 | 正确1: 6 | 7 | ```py 8 | import asyncio 9 | from aiohttp import ClientSession 10 | 11 | url = 'http://note.generals.space/aio' 12 | 13 | async def main(loop): 14 | session = ClientSession(loop = loop) 15 | resp = await session.get(url) 16 | await session.close() 17 | result = await resp.read() 18 | print(result) 19 | 20 | loop = asyncio.get_event_loop() 21 | loop.run_until_complete(main(loop)) 22 | 23 | ``` 24 | 25 | 正确2: 26 | 27 | ```py 28 | import asyncio 29 | from aiohttp import ClientSession 30 | 31 | url = 'http://note.generals.space/aio' 32 | 33 | async def main(): 34 | loop = asyncio.get_event_loop() 35 | session = ClientSession(loop = loop) 36 | resp = await session.get(url) 37 | await session.close() 38 | result = await resp.read() 39 | print(result) 40 | 41 | asyncio.run(main()) 42 | 43 | ``` 44 | 45 | 错误3: 46 | 47 | ```py 48 | import asyncio 49 | from aiohttp import ClientSession 50 | 51 | url = 'http://note.generals.space/aio' 52 | 53 | async def main(loop): 54 | session = ClientSession(loop = loop) 55 | resp = await session.get(url) 56 | await session.close() 57 | result = await resp.read() 58 | print(result) 59 | 60 | 61 | loop = asyncio.get_event_loop() 62 | asyncio.run(main(loop)) 63 | 64 | ``` 65 | 66 | 报错如下 67 | 68 | ``` 69 | ... 70 | RuntimeError: Timeout context manager should be used inside a task 71 | Unclosed client session 72 | client_session: 73 | ``` 74 | 75 | 问题分析 76 | 77 | 查看`asyncio.run()`源码会发现, ta其中有一句通过`loop = events.new_event_loop()`自行创建loop, 这可能会与我们通过`asyncio.get_event_loop()`得到的loop发生冲突. 78 | -------------------------------------------------------------------------------- /11.python3-concurrenct.futures真正的并行计算/readme.md: -------------------------------------------------------------------------------- 1 | 参考文章 2 | 3 | 1. [python异步并发模块concurrent.futures简析](http://lovesoo.org/analysis-of-asynchronous-concurrent-python-module-concurrent-futures.html) 4 | 5 | 2. [python concurrent.futures](https://www.cnblogs.com/kangoroo/p/7628092.html) 6 | 7 | 3. [python 3.7 官方文档 loop.run_in_executor](https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor) 8 | 9 | ## 0. 关于concurrent.futures 10 | 11 | 1. `python 3.x`中自带了`concurrent.futures`模块. 12 | 2. `python 2.7`需要安装`futures`模块, 使用命令`pip install futures`安装即可. 13 | 14 | 实际上, `concurrent.futures`模块与异步毫无关系, 这只是一种并行操作的高级操作, ta提供的`ProcessPoolExecutor`与`ThreadPoolExecutor`比`multiprocess`与`threading`实现的多进程和多线程简单太多了(不过没那么灵活). 15 | 16 | 可以看一下这个模块的使用, 因为这个模块对`Future`的应用很...单纯, 没有事件循环,协程对象,Task任务等概念的干扰. 17 | 18 | 另外, `asyncio`也有`loop.run_in_executor()`函数与`concurrent.futures`的`Executor`结合的使用方法, 可以查看参考文章3. 19 | 20 | ## 1. step1 21 | 22 | 在已经了解了`concurrent.futures`的情况下, 读官方文档给出的示例就非常简单了. `step1.py`就是(精简过)示例代码. 不过这个代码在windows平台3.7.3版本下, `ProcessPoolExecutor`模型会出问题, 只能在linux下运行. 23 | 24 | ``` 25 | concurrent.futures.process.BrokenProcessPool: A process in the process pool was terminated abruptly while the future was running or pending. 26 | ``` 27 | 28 | 另外, `run_in_executor()`貌似只能运行一个函数, 且必须通过`await`阻塞得到结果, 无法执行多个并行任务, 也无法后台执行...起码目前我还没有找到方法. 29 | 30 | ## 2. step2和step3 31 | 32 | 本来以为`run_in_executor()`是个鸡肋, 毕竟无法做到后台执行和多任务并行, 打脸了. 33 | 34 | 想像一下, 在协程任务中有一部分操作是cpu密集型的工作, 比如计算一个大数, 或者用for循环对对象或是字符串做一些处理, 一定会对其他协程造成影响. 35 | 36 | 在`step2.py`中, 构造了如下流程: 请求一个网页, 这个地址会随机在0-30秒后返回, 请求完成后调用回调函数, 在回调中用`time.sleep()`模拟了cpu耗时操作. 37 | 38 | 执行输出在`step2.py`底部给出, 不难看出`time.sleep()`对整个流程的影响. 39 | 40 | `step3.py`就是使用`Executor`提供解决方案的代码. 将协程任务中的cpu密集型操作, 通过`loop.run_in_executor()`放到额外的线程/进程池中, 避免阻塞其他协程的运行, 效果显著. 41 | 42 | `executor`初始化时`max_workers`值为4, 是因为我的电脑为4核, 可以最大限度地利用多核优势. 43 | 44 | 在使用`ProcessPoolExecutor()`时, 执行到`run_in_executor()`部分, 通过`ps`命令可以得到如下输出: 45 | 46 | ``` 47 | [root@b4e239c15bd1 /]# ps -ef 48 | UID PID PPID C STIME TTY TIME CMD 49 | root 1 0 0 12:05 pts/0 00:00:00 /bin/bash 50 | root 31 0 0 12:05 pts/2 00:00:00 /bin/bash 51 | root 50 31 0 12:06 pts/2 00:00:00 vim step3.py 52 | root 61 50 0 12:08 pts/2 00:00:00 sh 53 | root 62 61 5 12:08 pts/2 00:00:00 python step3.py 54 | root 63 62 0 12:08 pts/2 00:00:00 python step3.py 55 | root 64 62 0 12:08 pts/2 00:00:00 python step3.py 56 | root 65 62 0 12:08 pts/2 00:00:00 python step3.py 57 | root 66 62 0 12:08 pts/2 00:00:00 python step3.py 58 | root 70 16 0 12:08 pts/1 00:00:00 ps -ef 59 | ``` 60 | 61 | 而换成`ThreadPoolExecutor()`, 需要使用特定的`ps`选项, 才能看到关于线程的输出: 62 | 63 | ``` 64 | [root@b4e239c15bd1 /]# ps H -eo user,pid,ppid,tid,time,%cpu,cmd 65 | USER PID PPID TID TIME %CPU CMD 66 | root 1 0 1 00:00:00 0.0 /bin/bash 67 | root 31 0 31 00:00:00 0.0 /bin/bash 68 | root 79 31 79 00:00:00 0.6 python step3.py 69 | root 79 31 88 00:00:00 0.0 python step3.py 70 | root 79 31 89 00:00:00 0.0 python step3.py 71 | root 79 31 90 00:00:00 0.0 python step3.py 72 | root 79 31 91 00:00:00 0.0 python step3.py 73 | root 93 16 93 00:00:00 0.0 ps H -eo user,pid,ppid,tid,time,%cpu,cmd 74 | ``` 75 | 76 | 注意 77 | 78 | 1. 两者在`PID`列的不同. 79 | 2. `ProcessPoolExecutor()`在windows下仍然会出错. -------------------------------------------------------------------------------- /11.python3-concurrenct.futures真正的并行计算/step1.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor 3 | 4 | def blocking_cpu(): 5 | # CPU-bound operations will block the event loop: 6 | # in general it is preferable to run them in a 7 | # process pool. 8 | return sum(i * i for i in range(10 ** 2)) 9 | 10 | async def main(): 11 | loop = asyncio.get_event_loop() 12 | ## pool = ProcessPoolExecutor(max_workers = 4) 13 | pool = ThreadPoolExecutor(max_workers = 4) 14 | result = await loop.run_in_executor(pool, cpu_bound) 15 | print(result) ## 328350 16 | 17 | asyncio.run(main()) 18 | -------------------------------------------------------------------------------- /11.python3-concurrenct.futures真正的并行计算/step2.py: -------------------------------------------------------------------------------- 1 | import time 2 | import asyncio 3 | 4 | from aiohttp import ClientSession 5 | 6 | url = 'http://note.generals.space/aio' 7 | 8 | async def request_page(order, session): 9 | resp = await session.get(url) 10 | result = await resp.read() 11 | print('order: {:d}, result: {:s}'.format(order, str(result))) 12 | 13 | ## 这里是cpu密集型操作 14 | ## print('request {:d} to {:s} success'.format(order, url)) 15 | ## time.sleep(30) 16 | ## print('request {:d} to {:s} complete'.format(order, url)) 17 | 18 | async def main(): 19 | loop = asyncio.get_event_loop() 20 | session = ClientSession(loop = loop) 21 | coroutines = [] 22 | for i in range(4): 23 | co = loop.create_task(request_page(i, session)) 24 | coroutines.append(co) 25 | 26 | await asyncio.wait(coroutines) 27 | await session.close() 28 | 29 | start = time.time() 30 | 31 | asyncio.run(main()) 32 | 33 | end = time.time() 34 | print('cost %f' % (end - start)) 35 | 36 | ## 注释掉time.sleep()部分 37 | ## order: 0, result: b'{"delay":11}' 38 | ## order: 3, result: b'{"delay":14}' 39 | ## order: 2, result: b'{"delay":18}' 40 | ## order: 1, result: b'{"delay":19}' 41 | ## cost 19.040582 42 | 43 | ## 解开time.sleep()的注意 44 | ## order: 2, result: b'{"delay":15}' 45 | ## request 2 to http://note.generals.space/aio success 46 | ## request 2 to http://note.generals.space/aio complete 47 | ## order: 3, result: b'{"delay":17}' 48 | ## request 3 to http://note.generals.space/aio success 49 | ## request 3 to http://note.generals.space/aio complete 50 | ## order: 0, result: b'{"delay":20}' 51 | ## request 0 to http://note.generals.space/aio success 52 | ## request 0 to http://note.generals.space/aio complete 53 | ## order: 1, result: b'{"delay":18}' 54 | ## request 1 to http://note.generals.space/aio success 55 | ## request 1 to http://note.generals.space/aio complete 56 | ## cost 135.050332 -------------------------------------------------------------------------------- /11.python3-concurrenct.futures真正的并行计算/step3.py: -------------------------------------------------------------------------------- 1 | import time 2 | import asyncio 3 | import functools 4 | from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor 5 | 6 | from aiohttp import ClientSession 7 | 8 | ## executor = ThreadPoolExecutor(max_workers = 4) 9 | executor = ProcessPoolExecutor(max_workers = 4) 10 | 11 | url = 'http://note.generals.space/aio' 12 | 13 | def blocking_cpu(order, url): 14 | ## 这里是cpu密集型操作 15 | print('request {:d} to {:s} success'.format(order, url)) 16 | time.sleep(30) 17 | print('request {:d} to {:s} complete'.format(order, url)) 18 | 19 | async def request_page(loop, order, session): 20 | resp = await session.get(url) 21 | result = await resp.read() 22 | print('order: {:d}, result: {:s}'.format(order, str(result))) 23 | 24 | await loop.run_in_executor(executor, functools.partial(blocking_cpu, order, url)) 25 | 26 | async def main(): 27 | loop = asyncio.get_event_loop() 28 | session = ClientSession(loop = loop) 29 | coroutines = [] 30 | for i in range(4): 31 | co = loop.create_task(request_page(loop, i, session)) 32 | coroutines.append(co) 33 | 34 | await asyncio.wait(coroutines) 35 | await session.close() 36 | 37 | start = time.time() 38 | 39 | asyncio.run(main()) 40 | 41 | end = time.time() 42 | print('cost %f' % (end - start)) 43 | 44 | ## 输出 45 | ## order: 1, result: b'{"delay":3}' 46 | ## request 1 to http://note.generals.space/aio success 47 | ## order: 3, result: b'{"delay":5}' 48 | ## request 3 to http://note.generals.space/aio success 49 | ## order: 0, result: b'{"delay":13}' 50 | ## request 0 to http://note.generals.space/aio success 51 | ## order: 2, result: b'{"delay":25}' 52 | ## request 2 to http://note.generals.space/aio success 53 | ## request 1 to http://note.generals.space/aio complete 54 | ## request 3 to http://note.generals.space/aio complete 55 | ## request 0 to http://note.generals.space/aio complete 56 | ## request 2 to http://note.generals.space/aio complete 57 | ## cost 55.053128 58 | -------------------------------------------------------------------------------- /12.asyncio的call_XX函数族/call.py: -------------------------------------------------------------------------------- 1 | import time 2 | import asyncio 3 | 4 | from aiohttp import ClientSession 5 | 6 | url = 'http://localhost:3000/aio' 7 | 8 | def callback(loop, order): 9 | print('loop time: {:f}, request {:d} to {:s} success'.format(loop.time(), order, url)) 10 | 11 | async def request_page(loop, order, session): 12 | resp = await session.get(url) 13 | result = await resp.read() 14 | 15 | loop_time = loop.time() 16 | print('loop time: {:f}, order: {:d}, result: {:s}'.format(loop_time, order, str(result))) 17 | ## 指定时间后调用目标函数. 18 | ## loop.call_at(loop_time + 5, callback, loop, order) 19 | ## loop.call_later(5, callback, loop, order) 20 | ## loop.call_soon(callback, loop, order) 21 | loop.call_soon_threadsafe(callback, loop, order) 22 | 23 | async def main(loop): 24 | session = ClientSession() 25 | coroutines = [] 26 | for i in range(5): 27 | co = asyncio.create_task(request_page(loop, i, session)) 28 | coroutines.append(co) 29 | 30 | await asyncio.wait(coroutines) 31 | await session.close() 32 | 33 | start = time.time() 34 | 35 | loop = asyncio.get_event_loop() 36 | loop.run_until_complete(main(loop)) 37 | loop.stop() 38 | 39 | end = time.time() 40 | print('cost %f' % (end - start)) 41 | 42 | 43 | ## 输出 44 | ## loop time: 371982.687000, order: 4, result: b'{"delay":4}' 45 | ## loop time: 371982.687000, request 4 to http://localhost:3000/aio success 46 | ## loop time: 371983.687000, order: 1, result: b'{"delay":5}' 47 | ## loop time: 371983.687000, request 1 to http://localhost:3000/aio success 48 | ## loop time: 371985.687000, order: 2, result: b'{"delay":7}' 49 | ## loop time: 371985.687000, request 2 to http://localhost:3000/aio success 50 | ## loop time: 371991.687000, order: 3, result: b'{"delay":13}' 51 | ## loop time: 371991.687000, request 3 to http://localhost:3000/aio success 52 | ## loop time: 371996.687000, order: 0, result: b'{"delay":18}' 53 | ## loop time: 371996.687000, request 0 to http://localhost:3000/aio success 54 | ## cost 18.041001 55 | -------------------------------------------------------------------------------- /12.asyncio的call_XX函数族/call_safe.py: -------------------------------------------------------------------------------- 1 | import time 2 | import asyncio 3 | import threading 4 | 5 | from aiohttp import ClientSession 6 | 7 | url = 'http://localhost:3000/aio' 8 | 9 | def keep_loop(loop): 10 | asyncio.set_event_loop(loop) 11 | loop.run_forever() 12 | 13 | async def _request_page(order, session, loop): 14 | resp = await session.get(url) 15 | result = await resp.read() 16 | print('order: {:d}, result: {:s}'.format(order, str(result))) 17 | 18 | def request_page(order, session, loop): 19 | co = _request_page(order, session, loop) 20 | asyncio.run_coroutine_threadsafe(co, loop) 21 | 22 | try: 23 | loop = asyncio.get_event_loop() 24 | loopThread = threading.Thread(target=keep_loop, args=(loop,)) 25 | loopThread.setDaemon(True) 26 | loopThread.start() 27 | 28 | session = ClientSession(loop = loop) 29 | 30 | for i in range(5): 31 | loop.call_soon_threadsafe(request_page, i, session, loop) 32 | ## loop.call_soon()会卡住 33 | ## loop.call_soon(request_page, i, session, loop) 34 | 35 | ## join()时ctrl-c无法结束, 而while True可以. 36 | ## loopThread.join() 37 | while True: 38 | time.sleep(1) 39 | 40 | except KeyboardInterrupt as e: 41 | ## 以下两行可以注释掉, 不会报错 42 | asyncio.run_coroutine_threadsafe(session.close(), loop) 43 | time.sleep(1) 44 | loop.stop() 45 | 46 | 47 | ## 输出 48 | ## order: 3, result: b'{"delay":1}' 49 | ## order: 1, result: b'{"delay":9}' 50 | ## order: 0, result: b'{"delay":13}' 51 | ## order: 4, result: b'{"delay":22}' 52 | ## order: 2, result: b'{"delay":30}' 53 | -------------------------------------------------------------------------------- /12.asyncio的call_XX函数族/call_safe.py.1: -------------------------------------------------------------------------------- 1 | import time 2 | import asyncio 3 | import threading 4 | 5 | from aiohttp import ClientSession 6 | 7 | url = 'http://localhost:3000/aio' 8 | 9 | def keep_loop(loop): 10 | asyncio.set_event_loop(loop) 11 | loop.run_forever() 12 | 13 | async def _request_page(order, session, loop): 14 | resp = await session.get(url) 15 | result = await resp.read() 16 | print('order: {:d}, result: {:s}'.format(order, str(result))) 17 | 18 | def request_page(order, session, loop): 19 | co = _request_page(order, session, loop) 20 | asyncio.run_coroutine_threadsafe(co, loop) 21 | 22 | try: 23 | loop = asyncio.get_event_loop() 24 | loopThread = threading.Thread(target=keep_loop, args=(loop,)) 25 | loopThread.setDaemon(True) 26 | loopThread.start() 27 | 28 | session = ClientSession(loop = loop) 29 | 30 | for i in range(5): 31 | loop.call_soon_threadsafe(request_page, i, session, loop) 32 | 33 | ## join()时ctrl-c无法结束, 而while True可以. 34 | ## loopThread.join() 35 | while True: 36 | time.sleep(1) 37 | 38 | except KeyboardInterrupt as e: 39 | ## 以下两行可以注释掉, 不会报错 40 | ## asyncio.run_coroutine_threadsafe(session.close(), loop) 41 | ## time.sleep(1) 42 | loop.stop() 43 | 44 | 45 | ## 输出 46 | ## order: 0, result: b'{"delay":9}' 47 | ## request 0 to http://localhost:3000/aio success 48 | ## order: 3, result: b'{"delay":12}' 49 | ## request 3 to http://localhost:3000/aio success 50 | ## order: 2, result: b'{"delay":18}' 51 | ## request 2 to http://localhost:3000/aio success 52 | ## order: 1, result: b'{"delay":24}' 53 | ## request 1 to http://localhost:3000/aio success 54 | ## order: 4, result: b'{"delay":28}' 55 | ## request 4 to http://localhost:3000/aio success 56 | -------------------------------------------------------------------------------- /12.asyncio的call_XX函数族/readme.md: -------------------------------------------------------------------------------- 1 | 参考文章 2 | 3 | 1. [asyncio并发编程](http://www.cnblogs.com/lyq-biu/p/10486148.html) 4 | 5 | 2. [Python黑魔法 --- 异步IO( asyncio) 协程](https://www.jianshu.com/p/b5e347b3a17c) 6 | 7 | `asyncio`的`call_XX`函数族中的函数有如下几个 8 | 9 | - `call_at(when, callback, *args)`: 参数`when`是以`loop.time()`为基准的数值, 比如`loop.time() + 10`, 表示10s(协程中的时间, 不会阻塞其他协程的执行)后调用`callback`函数; 10 | - `call_later(delay, callback, *args)`: 参数`delay`为整数, 以当前`loop.time()`为基准, 猜测`call_later(10, callback)`等价于`call_at(loop.time() + 10, callback)`; 11 | - `call_soon(callback, *args)`: 猜测`call_soon(callback)`等价于`call_later(0, callback)`; 12 | - `call_soon_threadsafe`: 线程安全的操作, 使用方法与`call_soon`一致. 13 | 14 | ta们的作用个人感觉基本类似于js中的`setTimeout()`, 都是手动设置一个回调, 还可以控制回调的延迟时间, ta们都接受一个普通函数而非协程函数. 15 | 16 | ------ 17 | 18 | `call.py`为`call_XX`函数族的使用方法, 几乎相同. 19 | 20 | ~~ 关于`call_soon_threadsafe()`的使用场景, 一直没找到合适的, `call_safe.py.1`是使用子线程维护协程事件循环, 然后在主线程中动态添加任务. 但是在协程任务中无论使用`call_soon()`还是`call_soon_threadsafe()`都没有报错...先这样的, 以后再找找必须使用后者的情况. ~~ 21 | 22 | 好吧, 按照参考文章2, 又重新编写了`call_safe.py`, 在这个程序中, `call_soon()`方法的确不好使. 23 | -------------------------------------------------------------------------------- /13.异步文件读写及异步数据库操作/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | postgres-svc: 4 | image: postgres 5 | environment: 6 | - "POSTGRES_DB=mydb" 7 | - "POSTGRES_USER=my" 8 | - "POSTGRES_PASSWORD=123456" 9 | ports: 10 | - "5432:5432" 11 | -------------------------------------------------------------------------------- /13.异步文件读写及异步数据库操作/insert.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import psycopg2 4 | 5 | db_config = { 6 | 'host': 'localhost', 7 | 'port': '5432', 8 | 'database': 'mydb', 9 | 'user': 'my', 10 | 'password': '123456', 11 | } 12 | 13 | db_conn = psycopg2.connect(**db_config) 14 | 15 | def initdb(): 16 | cursor = db_conn.cursor() 17 | cursor.execute('create table if not exists test(id serial primary key, name varchar(50));') 18 | cursor.execute('delete from test;') 19 | db_conn.commit() 20 | cursor.close() 21 | 22 | def insert(i): 23 | name = 'name-{:d}'.format(i) 24 | cursor = db_conn.cursor() 25 | cursor.execute('insert into test(name) values(%s);', (name,)) 26 | db_conn.commit() 27 | cursor.close() 28 | 29 | initdb() 30 | 31 | start = time.time() 32 | for i in range(10000): insert(i) 33 | end = time.time() 34 | db_conn.close() 35 | 36 | print('cost %f' % (end - start)) 37 | -------------------------------------------------------------------------------- /13.异步文件读写及异步数据库操作/insert_async.py: -------------------------------------------------------------------------------- 1 | import time 2 | import asyncio 3 | 4 | import asyncpg 5 | 6 | db_config = { 7 | 'host': 'localhost', 8 | 'port': '5432', 9 | 'database': 'mydb', 10 | 'user': 'my', 11 | 'password': '123456', 12 | } 13 | 14 | db_pool = None 15 | sem = asyncio.Semaphore(100) 16 | 17 | async def initdb(): 18 | async with db_pool.acquire() as db_conn: 19 | await db_conn.execute('create table if not exists test(id serial primary key, name varchar(50));') 20 | await db_conn.execute('delete from test;') 21 | 22 | async def insert_async(i): 23 | ## 加锁 24 | async with sem: 25 | name = 'name-{:d}'.format(i) 26 | async with db_pool.acquire() as db_conn: 27 | await db_conn.execute('insert into test(name) values($1);', name) 28 | 29 | async def main(): 30 | global db_pool 31 | db_pool = await asyncpg.create_pool(**db_config) 32 | ## 先初始化数据库 33 | await initdb() 34 | 35 | cos = [] 36 | ## cos = [insert_async(i) for i in range(10)] 37 | for i in range(10000): 38 | co = asyncio.create_task(insert_async(i)) 39 | cos.append(co) 40 | await asyncio.wait(cos) 41 | 42 | start = time.time() 43 | loop = asyncio.get_event_loop() 44 | loop.run_until_complete(main()) 45 | end = time.time() 46 | 47 | print('cost %f' % (end - start)) 48 | -------------------------------------------------------------------------------- /13.异步文件读写及异步数据库操作/insert_async.py.1: -------------------------------------------------------------------------------- 1 | import time 2 | import asyncio 3 | 4 | import asyncpg 5 | 6 | db_config = { 7 | 'host': 'localhost', 8 | 'port': '5432', 9 | 'database': 'mydb', 10 | 'user': 'my', 11 | 'password': '123456', 12 | } 13 | 14 | db_conn = None 15 | sem = asyncio.Semaphore(100) 16 | 17 | async def initdb(): 18 | await db_conn.execute('create table if not exists test(id serial primary key, name varchar(50));') 19 | await db_conn.execute('delete from test;') 20 | 21 | async def insert_async(i): 22 | ## 加锁 23 | await sem.acquire() 24 | name = 'name-{:d}'.format(i) 25 | await db_conn.execute('insert into test(name) values($1);', name) 26 | sem.release() 27 | 28 | async def main(): 29 | global db_conn 30 | db_conn = await asyncpg.connect(**db_config) 31 | ## 先初始化数据库 32 | await initdb() 33 | 34 | cos = [] 35 | ## cos = [insert_async(i) for i in range(10)] 36 | for i in range(10000): 37 | co = asyncio.create_task(insert_async(i)) 38 | cos.append(co) 39 | await asyncio.wait(cos) 40 | 41 | start = time.time() 42 | loop = asyncio.get_event_loop() 43 | loop.run_until_complete(main()) 44 | end = time.time() 45 | 46 | print('cost %f' % (end - start)) 47 | -------------------------------------------------------------------------------- /13.异步文件读写及异步数据库操作/readme.md: -------------------------------------------------------------------------------- 1 | 参考文章 2 | 3 | 1. [Python Asyncio 精选资源列表](https://github.com/chenjiandongx/awesome-asyncio-cn) 4 | 5 | 2. [异步文件操作库 aiofiles](https://github.com/Tinche/aiofiles) 6 | 7 | 3. [postgres异步数据库驱动 asyncpg](https://github.com/MagicStack/asyncpg) 8 | 9 | 异步编程中, 最能利用的就是CPU与IO的性能差异了. 而异步IO的应用场景一般包括如下几种 10 | 11 | 1. 网络请求(socket, http), 可以用asyncio和aiohttp完成. 12 | 2. 本地文件读写, 可以用aiofiles完成. 13 | 3. 各种数据库驱动. 14 | 15 | `gevent`打的patch虽然可以让所有的标准库, 甚至包括一些第三方库(如requests)替换成异步操作, 但是用C作为底层的数据库驱动(比如`psycopg2`)是没办法起作用的; 16 | 17 | `asyncio`, `aiohttp`虽然提供了简单的socket服务和http服务, 但并未提供像`ssh`, `ftp`等应用层的异步操作. 各种数据库驱动其实都是基于socket的编程, 与`ssh`, `ftp`等同级, 这些工具只要基于`asyncio`, 就可以配合使用(主要是共用同一个事件循环). 18 | 19 | 我不清楚gevent是否有办法提供类似的方法, 提供各种数据库驱动. 不过目前好像没有, 除非ta能像twists一样, 有勇气把所有的网络操作重新写一遍... 20 | 21 | 由于官方的大力支持, `asyncio`提供的异步前景应该很好. 参考文章1中列出了各个领域的异步操作库, 基本上可以满足简单应用的开发. 22 | 23 | 但毕竟python并不是天生异步的语言, 很多第三方库用的都是同步的方法, 所以一般也只能在这些可见的地方做一些性能上的弥补. 24 | 25 | ## 1. 异步文件读写aiofiles 26 | 27 | `writefile.py`: 同步的方式循环创建10000的文件, 耗时大概为50s(由主机的性能不同而有所差异) 28 | 29 | `writefile_async.py.1`: (已废弃)使用`aiofiles`异步创建文件, 注意, 由于不做任何限制, `main()`函数中的for循环将在一瞬间完成, 会有10000个文件创建然后等待写入. 这样就会报如下错误 30 | 31 | ``` 32 | OSError: [Errno 24] Too many open files: 'data/9999' 33 | ``` 34 | 35 | 所以在批量进行异步操作时, 务必要限制并发数量, 比如示例07中的`semaphore`, 示例08中的协程池. 36 | 37 | `writefile_async.py`才是一个正确的示例. 只花了12s. 38 | 39 | ## 2. postgrse异步驱动asyncpg 40 | 41 | `insert.py`: 同步的方式循环插入10000条数据, 耗时大概88s. 42 | 43 | `insert_async.py.1`: (已废弃)使用`asyncpg`异步插入, 接受了上面的教训, 使用`semaphore`限制了并发数量, 但还是报了错, 如下 44 | 45 | ``` 46 | asyncpg.exceptions._base.InterfaceError: cannot perform operation: another operation is in progress 47 | ``` 48 | 49 | 按照官方issue[cannot perform operation: another operation is in progress](https://github.com/MagicStack/asyncpg/issues/258)中提到的解决方案, 重新编写了`insert_async.py`, 发现可行, 花费15.34s. 50 | -------------------------------------------------------------------------------- /13.异步文件读写及异步数据库操作/requirements.txt: -------------------------------------------------------------------------------- 1 | aiofiles>=0.4.0 2 | asyncpg>=0.18.3 3 | psycopg2-binary>=2.7.6.1 4 | -------------------------------------------------------------------------------- /13.异步文件读写及异步数据库操作/writefile.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | import time 3 | 4 | def writefile(i): 5 | filepath = 'data/{:d}'.format(i) 6 | filecontent = 'hello world for the {:d} times'.format(i) 7 | file = open(filepath, 'w') 8 | file.write(filecontent) 9 | file.close() 10 | 11 | ## 创建存储目录 12 | p = pathlib.Path('data') 13 | if not p.exists(): p.mkdir() 14 | 15 | start = time.time() 16 | for i in range(10000): writefile(i) 17 | end = time.time() 18 | 19 | print('cost %f' % (end - start)) 20 | -------------------------------------------------------------------------------- /13.异步文件读写及异步数据库操作/writefile_async.py: -------------------------------------------------------------------------------- 1 | import time 2 | import pathlib 3 | import asyncio 4 | 5 | import aiofiles 6 | 7 | sem = asyncio.Semaphore(100) 8 | 9 | async def writefile_async(i): 10 | ## 加锁 11 | async with sem: 12 | filepath = 'data/{:d}'.format(i) 13 | filecontent = 'hello world for the {:d} times'.format(i) 14 | async with aiofiles.open(filepath, mode='w') as file: 15 | await file.write(filecontent) 16 | 17 | ## 创建存储目录 18 | p = pathlib.Path('data') 19 | if not p.exists(): p.mkdir() 20 | 21 | start = time.time() 22 | 23 | async def main(): 24 | cos = [] 25 | ## cos = [writefile_async(i) for i in range(10)] 26 | for i in range(10000): 27 | co = asyncio.create_task(writefile_async(i)) 28 | cos.append(co) 29 | await asyncio.wait(cos) 30 | 31 | loop = asyncio.get_event_loop() 32 | loop.run_until_complete(main()) 33 | 34 | end = time.time() 35 | 36 | print('cost %f' % (end - start)) 37 | -------------------------------------------------------------------------------- /13.异步文件读写及异步数据库操作/writefile_async.py.1: -------------------------------------------------------------------------------- 1 | import time 2 | import pathlib 3 | import asyncio 4 | 5 | import aiofiles 6 | 7 | async def writefile_async(i): 8 | filepath = 'data/{:d}'.format(i) 9 | filecontent = 'hello world for the {:d} times'.format(i) 10 | file = await aiofiles.open(filepath, mode='w') 11 | await file.write(filecontent) 12 | await file.close() 13 | 14 | ## 创建存储目录 15 | p = pathlib.Path('data') 16 | if not p.exists(): p.mkdir() 17 | 18 | start = time.time() 19 | 20 | async def main(): 21 | cos = [] 22 | ## cos = [writefile_async(i) for i in range(10)] 23 | for i in range(10000): 24 | co = asyncio.create_task(writefile_async(i)) 25 | cos.append(co) 26 | await asyncio.wait(cos) 27 | 28 | loop = asyncio.get_event_loop() 29 | loop.run_until_complete(main()) 30 | 31 | end = time.time() 32 | 33 | print('cost %f' % (end - start)) 34 | -------------------------------------------------------------------------------- /14.aio_http_server/readme.md: -------------------------------------------------------------------------------- 1 | 参考文章 2 | 3 | 1. [aio server 官方文档](https://docs.aiohttp.org/en/stable/web.html) 4 | 2. [aiohttp 一个用于asyncio和Python的异步HTTP客户端/服务器](https://github.com/aio-libs/aiohttp-demos) 5 | - 这个示例提供了一个使用`aiohttp`实现的 websocket 接口, 先记下来, 以后可能用的上. 6 | 7 | 本示例使用`aiohttp`库实现了一个简单的 web 服务器, 为其他示例提供接口(以前是用 nodejs 写了一个脚本运行在服务器上的, 不过现在服务器没了, 在本地运行一个也一样). 8 | 9 | 对于一个`/aio`请求, 它会随机沉睡`1-30`秒再返回, 返回的内容是一个json字符串, 结构为`{delay: 沉睡的秒数}`, 示例中用这个接口来学习协程的使用方法. 10 | 11 | ``` 12 | $ python3 server.py 13 | ======== Running on http://0.0.0.0:3000 ======== 14 | (Press CTRL+C to quit) 15 | delay: 6 16 | ``` 17 | -------------------------------------------------------------------------------- /14.aio_http_server/requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp>=3.3.2 -------------------------------------------------------------------------------- /14.aio_http_server/server.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import asyncio 4 | from aiohttp import web 5 | 6 | async def aio_api(req): 7 | ## 随机沉睡 1-30s 再返回 8 | delay = random.randint(1, 30) 9 | resp = { 10 | 'delay': delay 11 | } 12 | print('delay: %d' % delay) 13 | ## 别用这个, 同步库的 sleep 不会让出 cpu, 协程服务器就没效果了. 14 | ## time.sleep(delay) 15 | await asyncio.sleep(delay) 16 | return web.json_response(resp) 17 | 18 | async def path_param_handler(req): 19 | ## 从 uri 路径中获取参数 20 | ## get() 方法的第2个参数貌似为默认值, 不过好像没什么用? 21 | name = req.match_info.get('name', 'Anonymous') 22 | text = "Hello, " + name 23 | return web.Response(text=text) 24 | 25 | route_list = [ 26 | web.get('/aio', aio_api), 27 | web.get('/{name}', path_param_handler) 28 | ] 29 | 30 | app = web.Application() 31 | app.add_routes(route_list) 32 | web.run_app(app, port = 3000) 33 | -------------------------------------------------------------------------------- /docs/asyncio接口方法.md: -------------------------------------------------------------------------------- 1 | 参考文章 2 | 3 | 5. [python 中的 asyncio 使用详解与异步协程的处理流程分析](https://testerhome.com/articles/19703) 4 | - 示例清晰, 代码很全 5 | - wait和gather有哪些区别: 6 | - `gather`是需要所有任务都执行结束, 如果某一个协程函数崩溃了, 则会抛异常, 都不会有结果。 7 | - `wait`可以定义函数返回的时机(`return_when`参数), 可以是`FIRST_COMPLETED`(第一个结束的), `FIRST_EXCEPTION`(第一个出现异常的), `ALL_COMPLETED`(全部执行完, 默认的) 8 | - `run_in_executor()`的使用示例 9 | - `asyncio.run_coroutine_threadsafe()`返回的是`concurrent.futures._base.Future`对象, 不能使用`await`, 但是可以添加回调`add_done_callback()` 10 | 11 | 12 | ## `loop.run_xxx()`家族: 事件主循环 13 | 14 | 1. `loop.run_forever(coroutine)` 15 | 2. `loop.run_until_complete(coroutine)` 16 | 3. `loop.run_in_executor(executor, func)`: 类似于`run_until_complete`, 不过执行的方法为普通函数, 而不是协程对象. 需要`await`阻塞执行. 17 | 4. `asyncio.run(coroutine)`: 类似于`loop.run_until_complete()`, 但是不能混用. 18 | 19 | ## 创建异步任务 20 | 21 | 1. `loop.create_task(coroutine)` 22 | 2. `asyncio.create_task(coroutine)`: 其实是调用了`loop.create_task()` 23 | 3. `asyncio.ensure_future()` 24 | 创建异步任务到事件循环中(但并不执行, 需要使用`wait`方法), 参数`coroutine`为async函数执行后得到的协程对象. 25 | 26 | ## wait&gather等待执行 27 | 28 | `asyncio.gather(coroutine1, coroutine2...)`: 同时执行多个协程函数, 每个协程算是一个参数; 29 | `asyncio.wait([coroutine1, coroutine2...], timeout)`: 与`gather`一样, 同时执行多个协程函数, 不过协程函数是以列表形式传入的, 还可以设置超时时间; 30 | `asyncio.wait_for(coroutine, timeout)`: 执行单个协程函数, 也可以设置超时. 31 | 32 | ## 33 | 34 | 6. `loop.call_soon_threadsafe(coroutine, loop)` 35 | 5. `asyncio.run_coroutine_threadsafe(coroutine, loop)`: 跨线程执行协程, 调用了`loop.call_soon_threadsafe()` 36 | 37 | ## add_done_callback() 38 | 39 | 使用`async`声明的`coroutine`对象并不能直接执行, 也不能添加回调函数什么的, 需要事先将其包装成`task`对象. 40 | 41 | ```py 42 | co = fetch_url(aioSession, _order, _url) 43 | task = asyncio.ensure_future(co) 44 | task.add_done_callback(callback) 45 | ``` 46 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # pyasync 2 | 3 | 参考文章 4 | 5 | 1. [Python黑魔法 --- 异步IO( asyncio) 协程](https://www.jianshu.com/p/b5e347b3a17c) 6 | - 参考文章1深入浅出地介绍了协程及其相关概念(loop事件循环, task任务, future结果对象), 层层递进, 容易理解. 相对于`廖雪峰`老师对`async/await`的两篇介绍文章, 更加系统, 且条理更加分明, 用作入门非常棒. 7 | 8 | ## 1. python异步与协程入门 9 | 10 | 首先要明确如下认知: 11 | 12 | ### 1.1 同步语言的异步库与原生标准库不兼容. 13 | 14 | 以python为例, 原生标准库的`time.sleep(5)`将占用 CPU 5秒钟, 在此CPU调度到该程序时, 这5秒钟将被浪费; 而异步库`asyncio.sleep(5)`将会让出CPU, 在CPU调度到此程序时, 可以执行其他协程中的任务. 15 | 16 | 同样, 异步库中的文件读写, 网络IO也与原生`read()`方法, `socket`及 http request 行为不同. 17 | 18 | ### 1.2 一般不同种类的异步库之间的方法不可混用 19 | 20 | `gevent`是用`patch`的方法让原生标准库拥有异步能力, 但对于某些较为底层的第3方库(如psycopg2数据库驱动, C语言编写)不能很好的支持; 21 | 22 | `tornado`框架的`http`与`socket`使用的是其内置的事件循环, 且貌似没有提供异步文件操作. 23 | 24 | 本系列文档着重讲解`asyncio`族的异步操作, 与`gevent`, `tornado`会有对比, 但不会详细介绍. 25 | 26 | ### 1.3 27 | 28 | 常规同步库, 单线程连续发送5个请求请求. 29 | 30 | ``` 31 | 请求开始 32 | ↓ 33 | |--5--|---7---|--5--|--5--|---7---| 34 | ↑ 35 | 请求结束 36 | ``` 37 | 38 | 使用异步库. 39 | 40 | ``` 41 | 请求开始 42 | ↓ 43 | |--5--| 44 | |---7---| 45 | |--5--| 46 | |--5--| 47 | |---7---| 48 | ↑ 49 | 请求结束 50 | ``` 51 | 52 | ## 2. 概念解释 53 | 54 | 自从写过 golang, 了解了ta的`GMP`模型以后, 对于 python 中的各种异步概念总算有了一个大致的理解, 这里用一种更加通俗(但可能不太准确)的方式来描述一下. 55 | 56 | 1. 协程(coroutine): 通过`async`关键字声明, 但ta本身无法执行. 普通函数`abc()`就可以开始执行, 但是协程函数`abc()`就只能得到一个对象, 拥有有各种属性. 57 | 58 | 2. 任务(task): 任务是对协程进一步封装, 单纯的协程对象虽然可以直接被放到事件循环中去执行, 但是有很多异步特性没有办法使用(比如绑定回调函数). 想要使用这些特性, 只能将`coroutine`协程封装成`task`任务(参考`loop.create_task()`方法). 59 | 60 | 3. 事件循环(event loop): 因为`coroutine`和`task`都只是对象, 没有办法执行, 那么怎么执行呢? 就是将ta们丢到事件循环中去. 61 | 62 | ------ 63 | 64 | 其实`event loop`可以理解为非异步场景中的线程池, 里面内置n个 worker, 这些 worker 不断从 task/coroutine 任务列表中取任务并执行. 65 | 66 | 下面一段代码是我工作中使用`threadpool`线程池的一小段示例. 67 | 68 | ```py 69 | from threadpool import ThreadPool, makeRequests 70 | 71 | ## 创建任务, 这里 worker 是一个函数, clusterList 列表类型, 其成员是 worker 函数的参数. 72 | ## reqs 是一个任务列表 73 | reqs = makeRequests(worker, clusterList) 74 | ## 将任务添加到线程池中 75 | for req in reqs: tPool.putRequest(req) 76 | logger.info('启动线程池... %d' % POOL_SIZE) 77 | ## 开始执行 78 | tPool.wait() 79 | ``` 80 | 81 | 我们在使用`asyncio`异步库时, 也只需要创建一个`event loop`, 并创建协程任务, 然后把任务放到事件循环中即可(`worker`都不用管了). 82 | 83 | 而且**回调**也没有那么可怕, 常规的多线程编程其实也算是异步, 想一想在那种场景下是怎么解决的? 其实就是回调函数, 或是结果队列, 总之解决方案很多. 84 | 85 | ### 2.1 await 86 | 87 | `await`这个还是单独说一下, 这个关键字用于挂起阻塞的异步调用接口. 88 | 89 | 一般来说, 需要`await`的都是`sleep`, 磁盘IO, 网络IO等需要 cpu 空转以等待的操作,. 在`await`后, 语句会正常执行, 但是 cpu 会让出, 去执行其他协程. 等到`await`的操作有了结果, 返回时异步框架会回到这个地方继续执行. 比如`await asyncio.sleep(5)`, `await aioSession.get(url)`等. 90 | 91 | 最初接触这个概念还是比较萌b的, 不懂ta在说什么...(@_@), 现在终于稍微明白一点了. 92 | 93 | 假设有如下协程函数, 创建两个协程对象并放入到事件循环中, 你猜会怎样? 94 | 95 | ```py 96 | async def do_something(): 97 | a = 0 98 | for i in range(10000000): a += i 99 | ``` 100 | 101 | 这其实就是我们所说的 cpu 密集型任务, 一个协程任务一旦获取到 cpu 就不会让出了, 这就会导致另一个协程任务迟迟得不到执行机会而"饿死". 102 | 103 | ``` 104 | 请求开始 直到 for 循环结束 105 | ↓ ↓ 106 | |--------------------------------...| 107 | |------| // 第2个任务根本没机会执行. 108 | ``` 109 | 110 | 适用于`asyncio`的是IO密集型的任务, 如下 111 | 112 | ```py 113 | async def do_something(url): 114 | await aiohttpSession.get(url) 115 | ``` 116 | 117 | 创建`10000000`个协程对象, 放入到事件循环, 程序会立刻创建`10000000`个 http 请求. 因为`aiohttpSession.get()`是一个异步的操作(由`aiohttp`提供), `await`在发出请求后让出 cpu, 不阻塞等待ta的执行结果, cpu 会立刻切换到**同线程的其他协程**, 实现 cpu 资源的最大利用. 118 | 119 | ...当然上面这种操作还是很危险的, 虽然协程很轻量, 占用 cpu 内存都很少, 但是`10000000`这个数值还是太大了, 会把服务器的其他资源耗尽的(比如打开文件数, 端口数量等). 而且目标服务器也一定会被搞崩溃的, 毕竟千万级并发了. 120 | 121 | > 注意: 对于一个异步的协程对象, 如果不使用`await`执行ta, 那这个协程就根本不会执行. 122 | 123 | > 另外也不要尝试`await sleep(5)`这种操作了, `await`后面只能接异步协程对象, 任何同步库的方法都不能与异步`await`共用的. 124 | 125 | 在 golang 中, 什么时候需要挂起协程(即`await`一个协程)是不需要开发者关心的, 因为 golang 在底层提供了对协程的支持, 在进行诸如文件读写, 睡眠等系统调用前就可以判断此操作是否需要让出 cpu 了(底层的系统调用函数是有限的, golang 只需要关注有限的几个就可以了). 126 | 127 | 而 python 本身并不支持协程, 目前所有的异步框架都是上层的封装, 前期`yield`关键字应该在底层借助了类似 linux 的`sched_yield`系统调用, 实现了主动让出 cpu 的行为. 128 | 129 | ------ 130 | 131 | 还有一点, 如果你学过编译原理相关的东西, 了解`PC`寄存器存储着下一条指令的地址, 就会知道, 在`await`让出 cpu 后再让 ta 回到原来的位置继续执行大致是如何实现的了. 不只是`PC`, 协程在执行过程中各种局部变量等执行现场信息, 应该都是存储在`coroutine`对象中的, 其底层原理还有待探究. 132 | 133 | ## 3. 示例列表 134 | 135 | 下述示例中有多个示例都用到了`http://localhost:3000/aio`这个接口, 是用**示例14**提供的. 对于一个`/aio`请求, 它会随机沉睡`1-30`秒再返回(模拟服务端的处理耗时), 返回的内容是一个json字符串, 结构为`{delay: 沉睡的秒数}`. 136 | 137 | 1. [协程模型回显服务器](./01.协程模型回显服务器/readme.md) 138 | - 使用`asyncio`编写的简单的echo回显`socket`服务, 包括服务端与客户端. 139 | - 原生socket, asyncio提供的`sock_accept()`, `sock_recv()`与`sock_send()`收发函数. 140 | 2. [简单协程示例](./02.简单协程示例/readme.md) 141 | - 使用aiohttp库进行简单的http get请求 142 | - 进阶示例, 多http请求并发调用. 143 | - asyncio中的回调函数(`add_done_callback()`)与超时时间(`asyncio.wait()`)设置 144 | 3. [动态添加任务](./03.动态添加任务/readme.md) 145 | - 线程+协程, 动态生成异步协程任务放到事件循环 146 | 4. [tornado事件循环初试](./04.tornado事件循环初试/readme.md) 147 | - tornado实现的事件循环, 极简示例 148 | 5. [动态添加任务改进](./05.动态添加任务改进/readme.md) 149 | - 使用asyncio提供的异步队列, 实现生产者消费者模型 150 | 6. [协程超时-装饰器](./06.协程超时-装饰器/readme.md) 151 | - 一时无聊, 将异步请求中的超时设置抽象成了装饰器, 可重复使用. 152 | 7. [限制协程并发数量-semaphore](./07.限制协程并发数量-semaphore/readme.md) 153 | - 类似于进程间同步机制的`asyncio.Semaphore()`, 限制协程任务的并发数量, 可防止过载. 154 | 8. [协程池-asyncpool](./08.协程池-asyncpool/readme.md) 155 | - 还没来得及写, 但觉得很有必要, 协程虽然占用资源少, 但也不能无限创建. 156 | 9. [协程模型回显服务器(二)](./09.协程模型回显服务器(二)/readme.md) 157 | - asyncio的`start_server()`与loop的`create_server()`, 替换原生socket()来启动服务端. 158 | 10. [python3.7中asyncio协程回显示例](./10.python3.7中asyncio协程回显示例/readme.md) 159 | - python3.7中与3.6相比 asyncio的api有些变化, 这里是一个简单示例. 160 | 11. [python3-concurrenct.futures真正的并行计算](./11.python3-concurrenct.futures真正的并行计算/readme.md) 161 | - `concurrenct.futures()`多进程模型对CPU密集型任务的作用, 最好先理解`concurrenct.futures()`本身的作用. 162 | - `loop.run_in_executor()`的使用. 163 | 12. [asyncio的call_XX函数族](./12.asyncio的call_XX函数族/readme.md) 164 | - call_XXX函数族, 设置协程任务回调操作 165 | 13. [异步文件读写及异步数据库操作](./13.异步文件读写及异步数据库操作/readme.md) 166 | - 选用了两个异步库, 进行异步文件读写与异步数据库读写, 兼容于asyncio的事件循环. 167 | 14. [aiohttp.web 简单的异步web服务器](./14.aio_http_server/readme.md) 168 | - 其他示例请求的`http://localhost:3000/aio`接口, 就是这个服务器提供的. 169 | --------------------------------------------------------------------------------