python aiohttp socketio 中是否可能存在竞争条件?

Is it possible to have race condition in python aiohttp socketio?

我正在编写类似于以下代码的代码。有时程序会停止工作,或者我会收到有关 socketio 会话访问的奇怪错误。慢慢觉得可能是race conditions。

更多的是伪代码。我想证明,我从多个协程访问全局共享状态和 socketio 会话。

import asyncio as aio
from aiohttp import web
import socketio


app = web.Application()
sio = socketio.AsyncServer()

app["sockets"] = []

@sio.on("connect")
async def connect(sid):
    app["sockets"].append(sid)

@sio.on("disconnect")
async def disconnect(sid):
    app["sockets"].remove(sid)

@sio.on("set session")
async def set_session(sid, arg):
    await sio.save_session(sid, {"arg": arg})

async def session_route(req):
    data = await req.json()
    for sid in app["sockets"]:
        await sio.save_session(sid, {"arg": data["arg"]})
    return web.Response(status=200)

if __name__ == '__main__':
    web.run_app(app)

这里肯定有问题:

for sid in app["sockets"]:  # you are iterating over a list here
    await sio.save_session(...)  # your coroutine will yield here

您正在迭代列表 app["sockets"],并且在每次迭代中您都使用 await 关键字。当使用 await 关键字时,你的协程被暂停,事件循环检查是否可以执行或恢复另一个协程。

假设 connect(...) 协同程序 运行 而 session_route 正在等待。

app["sockets"].append(sid)  # this changed the structure of the list

connect(...) 更改了列表的结构。这 可以 使该列表当前存在的所有迭代器无效。 disconnect(...) 协程也是如此。

所以要么不要修改列表,要么至少不要在列表更改后重用迭代器。后一种解决方案在这里更容易实现:

for sid in list(app["sockets"]):
    await sio.save_session(...)

现在 for 循环遍历原始列表的副本。现在更改列表将不会 "disturb" 副本。

但是请注意,副本无法识别列表中的添加和删除。

所以,简而言之,你的问题的答案是,但它与async io无关。同样的问题很容易出现在同步代码中:

for i in my_list:
    my_list.remove(1)  # don't do this