Python 字典是否支持异步操作安全?

9

我在我的Python应用程序中创建了一个字典,保存数据,并且有两个任务并发运行并从外部API获取数据。一旦它们获取到数据,它们就会更新该字典——每个任务使用字典的不同键。

我想知道这个字典是否是异步安全的,或者在读取/更新字典时是否需要加锁?

这些任务还会每次读取上次保存的值。

my_data = {}
asyncio.create_task(call_func_one_coroutine)
asyncio.create_task(call_func_two_coroutine)

async def call_func_one_coroutine():
  data =  await goto_api_get_data()
  my_data['one'] = data + my_data['one']


async def call_func_two_coroutine():
  data =  await goto_api_another_get_data()
  my_data['two'] = data + my_data['two']


这个有帮助吗:https://dev59.com/ZGw05IYBdhLWcg3w6F8w - Dani Mesejo
谢谢。这个问题涉及线程安全性,我的问题是关于asyncio是否安全。 - InfoLearner
1个回答

21
我想了解字典是否异步安全,或者在读/更新字典时是否需要加锁?由于Asyncio基于协作式多任务处理,并且只能在明确的await表达式或async with和async for语句中切换任务。由于更新单个字典永远不会涉及等待(await必须在更新开始之前完成),因此从异步多任务处理的角度来看,它是完全原子的,您无需对其进行锁定。这适用于从异步代码访问的所有数据结构。
另一个没有问题的例子:
# correct - there are no awaits between two accesses to the dict d
key = await key_queue.get()
if key in d:
    d[key] = calc_value(key)

一个字典修改不支持异步安全的示例,涉及到使用await分隔的多个对字典的访问。例如:

# incorrect, d[key] could appear while we're reading the value,
# in which case we'd clobber the existing key
if key not in d:
    d[key] = await read_value()

要纠正它,您可以在 await 后添加另一个检查,或者使用显式锁:

# correct (1), using double check
if key not in d:
    value = await read_value()
    # Check again whether the key is vacant. Since there are no awaits
    # between this check and the update, the operation is atomic.
    if key not in d:
        d[key] = value

# correct (2), using a shared asyncio.Lock:
async with d_lock:
    # Single check is sufficient because the lock ensures that
    # no one can modify the dict while we're reading the value.
    if key not in d:
        d[key] = await read_value()

谢谢。那很有道理。假设有2个函数。当我们执行await func1()时,事件循环执行的步骤是什么?1. 执行func1,运行直到看到await或完成。如果它看到await,则暂停func1并切换并运行func2或2. 暂停func1,切换并运行func2,一旦func2执行await或完成,则恢复func1?是否有一种方法可以查看await的代码? - InfoLearner
@InfoLearner 这是一个单独的问题,但如果你想了解await的工作原理,请看一下这个讲座(使用yield from,但完全适用于await)或者这个讲座,它专门讲解了await的展开。 - user4815162342
1
@InfoLearner 如果我理解你的问题正确(你没有说明func2是如何启动的),那么第一种情况就是发生的。当你await func1()时,Python会立即开始执行func1而不会降低到事件循环。只有当func1选择挂起时,等待者才会挂起,因此等待者的等待者也会挂起,依此类推,最终达到事件循环。事件循环将决定下一个要运行的协程,如果有的话。(如果没有可运行的协程,则事件循环将轮询已注册的文件描述符并休眠,直到下一个IO/超时事件。) - user4815162342

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接