【问题标题】:Understanding Python contextvars了解 Python 上下文变量
【发布时间】:2020-07-26 21:19:01
【问题描述】:

关于以下SO answer 。为了了解使用 Contextvars 和不使用 Contextvars 之间的区别,我进行了一些更改。

我预计变量myid 在某些时候会损坏,但将范围更改为更高的数字似乎根本没有影响。

import asyncio
import contextvars

# declare context var
request_id = contextvars.ContextVar('Id of request.')


async def some_inner_coroutine(myid):
    # get value
    print('Processed inner coroutine of myid   : {}'.format(myid))
    print('Processed inner coroutine of request: {}'.format(request_id.get()))
    if myid != request_id.get():
        print("ERROR")


async def some_outer_coroutine(req_id):
    # set value
    request_id.set(req_id)

    await some_inner_coroutine(req_id)

    # get value
    print('Processed outer coroutine of request: {}'.format(request_id.get()))


async def main():
    tasks = []
    for req_id in range(1, 1250):
        tasks.append(asyncio.create_task(some_outer_coroutine(req_id)))

    await asyncio.gather(*tasks)


if __name__ == '__main__':
    asyncio.run(main())

结果

Processed inner coroutine of myid   : 1
Processed inner coroutine of request: 1
Processed outer coroutine of request: 1
Processed inner coroutine of myid   : 2
Processed inner coroutine of request: 2
Processed outer coroutine of request: 2
Processed inner coroutine of myid   : 3
Processed inner coroutine of request: 3
Processed outer coroutine of request: 3
Processed inner coroutine of myid   : 4
Processed inner coroutine of request: 4
Processed outer coroutine of request: 4
...
...
Processed inner coroutine of myid   : 1244
Processed inner coroutine of request: 1244
Processed outer coroutine of request: 1244
Processed inner coroutine of myid   : 1245
Processed inner coroutine of request: 1245
Processed outer coroutine of request: 1245
Processed inner coroutine of myid   : 1246
Processed inner coroutine of request: 1246
Processed outer coroutine of request: 1246
Processed inner coroutine of myid   : 1247
Processed inner coroutine of request: 1247
Processed outer coroutine of request: 1247
Processed inner coroutine of myid   : 1248
Processed inner coroutine of request: 1248
Processed outer coroutine of request: 1248
Processed inner coroutine of myid   : 1249
Processed inner coroutine of request: 1249
Processed outer coroutine of request: 1249

我应该如何更改才能看到变量 myid 的意外行为?

【问题讨论】:

  • 为什么要将它们创建为任务,如果你收集它们,它们会立即开始运行?只做后者
  • 我不明白。请您详细说明一下吗?
  • contextvars 不是局部变量的替代品,它们可以正常工作。它们是 global 变量的替代品,在所有任务之间共享。如果myid 是一个全局变量(比如通过上下文管理器设置),那么它将只能保存一个值。使用线程,您可以引入线程局部变量并具有线程局部的“全局”状态。 Contextvars 等价于任务(也支持回调)。
  • 所以基本上我的任务在同一个线程中运行并共享相同的上下文变量。如果我有多个线程,那么我会看到上下文变量有多么有用。我说的对吗?
  • 没有。 contextvar 的重点是单个 var 在不同的异步任务和类似任务的上下文中具有不同的值,所有这些都在同一个线程上运行。 contextvars 对多线程有用,因为它们是为明确单线程的异步设计的。

标签: python multithreading python-asyncio python-contextvars


【解决方案1】:

上下文变量很方便,当您需要沿着调用链传递一个变量以便它们共享相同的上下文时,如果在 并发。 上下文变量可以在多线程代码和异步(使用协程)中用作全局变量的替代方案。

asyncio 原生支持上下文变量,无需任何额外配置即可使用。因为在创建 Task 时,它会复制当前上下文,然后在复制的上下文中运行其协程:

# asyncio/task.py
class Task:
    def __init__(self, coro):
        ...
        # Get the current context snapshot.
        self._context = contextvars.copy_context()
        self._loop.call_soon(self._step, context=self._context)

    def _step(self, exc=None):
        ...
        # Every advance of the wrapped coroutine is done in
        # the task's context.
        self._loop.call_soon(self._step, context=self._context)
        ...

以下是您的示例,显示了与上下文变量相比全局变量的损坏:

import asyncio
import contextvars

# declare context var
current_request_id_ctx = contextvars.ContextVar('')
current_request_id_global = ''


async def some_inner_coroutine():
    global current_request_id_global

    # simulate some async work
    await asyncio.sleep(0.1)

    # get value
    print('Processed inner coroutine of request: {}'.format(current_request_id_ctx.get()))
    if current_request_id_global != current_request_id_ctx.get():
        print(f"ERROR! global var={current_request_id_global}")


async def some_outer_coroutine(req_id):
    global current_request_id_global

    # set value
    current_request_id_ctx.set(req_id)
    current_request_id_global = req_id

    await some_inner_coroutine()

    # get value
    print('Processed outer coroutine of request: {}\n'.format(current_request_id_ctx.get()))


async def main():
    tasks = []
    for req_id in range(1, 10000):
        tasks.append(asyncio.create_task(some_outer_coroutine(req_id)))

    await asyncio.gather(*tasks)


if __name__ == '__main__':
    asyncio.run(main())

输出:

...
Processed inner coroutine of request: 458
ERROR! global var=9999
Processed outer coroutine of request: 458

Processed inner coroutine of request: 459
ERROR! global var=9999
Processed outer coroutine of request: 459
...

使用threading.local() 转换代码的示例可以在PЕP 567 中找到

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-02-16
    • 2011-04-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多