contextvars 用于管理、存储和访问上下文相关的状态。
在了解 contextvars 之前,我们先了解一下 ThreadLocal。
ThreadLocal
顾名思义,ThreadLocal 就是本地线程变量,它可用来管理当前线程中的变量。
主要有以下几种使用场景:
- 跨层传输数据的时候,可以简化参数传递,打破层与层之间的屏障
- 隔离线程之间的数据
用法大致如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import threading import time
global_data = threading.local()
def func(): if not hasattr(global_data, 'num'): global_data.num = 0 for _ in range(100): global_data.num += 1 print(threading.current_thread().getName(), global_data.num)
if __name__ == '__main__': for _ in range(20): thread = threading.Thread(target=func) thread.start() time.sleep(10)
|
ThreadLocal 不适合使用的场景:
- 协程:一些阻塞的操作,我们经常会使用到协程(如 gevent)进行处理;
- 线程池:在 WSGI 中,并不能保证每次都产生一个新的线程来处理请求, 那么上一个请求遗留下来的数据将会污染当前请求。
contextvars
ThreadLocal 虽然在线程中隔离效果特别好,但是在协程中效果并不理想,
于是 contextvars 便出现了,它用于管理、存储和访问上下文相关的状态。
contextvars 在 asyncio 中有原生的支持并且无需任何额外配置即可被使用。
例如,以下是一个简单的回显服务器,它使用 contextvars 来将客户端的地址传递到请求处理的协程中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import asyncio import contextvars
client_addr_var = contextvars.ContextVar('client_addr')
def render_goodbye():
client_addr = client_addr_var.get() return f'Good bye, client @ {client_addr}\n'.encode()
async def handle_request(reader, writer): addr = writer.transport.get_extra_info('socket').getpeername() client_addr_var.set(addr)
while True: line = await reader.readline() print(line) if not line.strip(): break writer.write(line)
writer.write(render_goodbye()) writer.close()
async def main(): srv = await asyncio.start_server( handle_request, '127.0.0.1', 8081)
async with srv: await srv.serve_forever()
asyncio.run(main())
|