🐙
FastAPIを非同期で使うときグローバル変数は使用できない
概要
FastAPIを非同期で実装する時に、グローバル変数を使用すると挙動が不安定になるという記事です。
FastAPIでリクエストに応じてグローバル変数を書き換える処理を実装し、同時実行したときに途中で変数が上書きされてしまうことを確認しました。
原因としては、グローバル変数はFastAPIのリクエスト間で共有されているからです。
具体的な対策としては、Path Operation(デコレーションされたルーティングの関数)の中で変数を宣言して使うという方法などがあります。
不安定な挙動を確認する
GETリクエストが来たらグローバル変数にランダムな値としてuuidを代入します。
1~3秒間待機し、先ほど代入した値を取り出して同じ値かどうかを確認します。
app = FastAPI()
var = "" # ここでグローバル変数を宣言
@app.get("/")
async def root():
global var
var = str(uuid4())
before_var = var
await asyncio.sleep(random.randint(1,3))
after_bar = var
# beforeとafterが同じになっているかを確認
print("check:", "ok" if before == after else "not equal")
確認
FastAPIに対して100リクエストを同時実行して確認します。
ab -n 100 -c 100 -m POST http://127.0.0.1:3000/
結果
1回目はok
と出ているのでbeforeとafterで同じ値でしたが、2回目以降はnot equal
となり、beforeとafterの値が異なります。
つまり、待機中に他のリクエストによってグローバル変数が上書きされたということです。
check: ok
INFO: 127.0.0.1:53072 - "GET / HTTP/1.0" 200 OK
check: not equal
INFO: 127.0.0.1:53079 - "GET / HTTP/1.0" 200 OK
check: not equal
INFO: 127.0.0.1:53080 - "GET / HTTP/1.0" 200 OK
check: not equal
INFO: 127.0.0.1:53082 - "GET / HTTP/1.0" 200 OK
check: not equal
INFO: 127.0.0.1:53083 - "GET / HTTP/1.0" 200 OK
check: not equal
INFO: 127.0.0.1:53088 - "GET / HTTP/1.0" 200 OK
check: not equal
INFO: 127.0.0.1:53089 - "GET / HTTP/1.0" 200 OK
check: not equal
INFO: 127.0.0.1:53092 - "GET / HTTP/1.0" 200 OK
check: not equal
INFO: 127.0.0.1:53096 - "GET / HTTP/1.0" 200 OK
check: not equal
INFO: 127.0.0.1:53097 - "GET / HTTP/1.0" 200 OK
check: not equal
INFO: 127.0.0.1:53099 - "GET / HTTP/1.0" 200 OK
check: not equal
INFO: 127.0.0.1:53101 - "GET / HTTP/1.0" 200 OK
check: not equal
INFO: 127.0.0.1:53102 - "GET / HTTP/1.0" 200 OK
check: not equal
INFO: 127.0.0.1:53103 - "GET / HTTP/1.0" 200 OK
check: not equal
INFO: 127.0.0.1:53104 - "GET / HTTP/1.0" 200 OK
check: not equal
INFO: 127.0.0.1:53107 - "GET / HTTP/1.0" 200 OK
check: not equal
INFO: 127.0.0.1:53110 - "GET / HTTP/1.0" 200 OK
check: not equal
INFO: 127.0.0.1:53121 - "GET / HTTP/1.0" 200 OK
check: not equal
INFO: 127.0.0.1:53125 - "GET / HTTP/1.0" 200 OK
check: not equal
INFO: 127.0.0.1:53134 - "GET / HTTP/1.0" 200 OK
check: not equal
INFO: 127.0.0.1:53135 - "GET / HTTP/1.0" 200 OK
check: not equal
INFO: 127.0.0.1:53140 - "GET / HTTP/1.0" 200 OK
check: not equal
INFO: 127.0.0.1:53141 - "GET / HTTP/1.0" 200 OK
check: not equal
INFO: 127.0.0.1:53143 - "GET / HTTP/1.0" 200 OK
check: not equal
INFO: 127.0.0.1:53144 - "GET / HTTP/1.0" 200 OK
...
まとめ
FastAPIではグローバル変数はリクエスト間で共有されているため、このような結果となりました。
グローバル変数の使用はスレッドセーフではないため、非同期処理では特別な理由がない限り使用を避けた方が良いと思います。
簡単な対策としては、Path Operation内で変数を宣言するなどがあります。
ありがとうございました。
Discussion