Skip to content

Instantly share code, notes, and snippets.

@sigridjineth
Last active December 22, 2023 01:49
Show Gist options
  • Save sigridjineth/baca6160a45fc88e084ca3f05cd2fc38 to your computer and use it in GitHub Desktop.
Save sigridjineth/baca6160a45fc88e084ca3f05cd2fc38 to your computer and use it in GitHub Desktop.
python thread vars

python threadvars

  • WSGI와 ASGI 환경에서의 threading.localcontextvars의 사용 및 차이점

  • WSGI 환경에서는 각 요청마다 별개의 스레드가 생성되기 때문에 threading.local을 사용해 스레드별로 독립된 데이터 저장소를 관리할 수 있음. 하지만, ASGI 환경에서는 하나의 스레드 안에서 여러 태스크가 동시에 실행되기 때문에 threading.local로는 데이터의 격리가 어려워짐.

  • 이 문제를 해결하기 위해 contextvars가 등장했음. contextvars는 동시성 코드에서 현재 컨텍스트에 대한 데이터를 독립적으로 관리할 수 있는 방법을 제공함. 각각의 비동기 태스크가 자신만의 컨텍스트를 가지고, 이 컨텍스트는 다른 태스크와 공유되지 않아 데이터가 서로 침해받지 않음.

  • 아래 예제 코드는 contextvars를 사용하여 여러 "평행세계"(비동기 태스크)에서 각각의 데이터를 독립적으로 관리하는 방법을 보여주고 있음. 이는 Python 3.7부터 사용 가능한 기술로, 동시성 코드에서 컨텍스트 관리를 더욱 쉽고 명확하게 할 수 있게 해줌.

# https://velog.io/@kjyggg/python-uvicorn-%EC%8B%A4%ED%96%89-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4
import contextvars
import asyncio

## who_am_i 표식이 만들어짐
who_am_i = contextvars.ContextVar("who_am_i")


def do_something(some):
    who_am_i.set(some)

async def universeA():
    ## 표식에 universeA 남기기
    who_am_i.set("universeA")

    ## 사진찍기-copy_context
    context = contextvars.copy_context()

    print(f"in universeA:{list(context.items())}")

    ## 사진찍은 시점으로 들어가기, universeA 를 destroy
    context.run(do_something, "destory universeA")

    print(f"after destory context-inner who_am_i:{context.get(who_am_i)}")
    print(f"after destory context-outer who_am_i:{who_am_i.get()}")


async def universeB():
    who_am_i.set("universeB")
    context = contextvars.copy_context()
    print(f"in universeB:{list(context.items())}")

async def universeC():
    who_am_i.set("universeC")
    context = contextvars.copy_context()
    print(f"in universeC:{list(context.items())}")


async def universeD():
    who_am_i.set("universeD")
    context = contextvars.copy_context()
    print(f"in universeD:{list(context.items())}")


async def universeE():
    who_am_i.set("universeE")
    context = contextvars.copy_context()
    print(f"in universeE:{list(context.items())}")

async def cross_universe():
    a = asyncio.create_task(universeA())
    b = asyncio.create_task(universeB())
    c = asyncio.create_task(universeC())
    d = asyncio.create_task(universeD())
    e = asyncio.create_task(universeE())
    await asyncio.gather(a, b, c, d, e)

if __name__ == "__main__":
    asyncio.run(cross_universe())
  • FastAPI와 같은 비동기 웹 프레임워크는 기본적으로 ASGI 서버가 필요함.
  • ASGI는 WSGI(Web Server Gateway Interface)의 비동기 버전으로, 한 쓰레드에서 여러 코루틴이 동시에 처리될 수 있음.
  • WSGI 환경에서는 각 요청이 별도의 스레드에서 처리되어, threading.local을 사용해 각 스레드별로 데이터를 독립적으로 관리할 수 있었음. 그러나 ASGI에서는 하나의 스레드 내에서 여러 코루틴이 동시에 실행되므로, threading.local을 사용하면 요청 간 컨텍스트가 섞일 수 있음.
  • 이를 해결하기 위해 contextvars가 도입됨. contextvars는 각 비동기 태스크별로 독립적인 컨텍스트를 관리할 수 있게 해줌.
  • FastAPI는 ASGI 기반의 프레임워크임. 이를 위해 ASGI 서버가 필요하며, 대표적으로 uvicorn, hypercorn, daphne 등이 사용될 수 있음. 이러한 서버들은 요청을 비동기적으로 처리해 여러 코루틴이 동시에 실행되게 함.
  • FastAPI와 같은 비동기 웹 애플리케이션에서는 contextvars를 사용해 각 요청이나 태스크별로 고유한 컨텍스트를 유지하고 관리할 수 있음. 이를 통해 요청이나 작업별로 필요한 데이터를 안전하게 분리하여 사용할 수 있음.
  • FastAPI의 미들웨어에서 클라이언트의 각 요청을 처리할 때 contextvars를 사용하여 요청별 고유한 정보(예: 사용자 ID, 요청 ID 등)를 저장하고 관리할 수 있음. 이는 요청의 생명주기 동안 일관된 데이터 관리가 가능함.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment