8.9. ThreadSafeFlag¶
asyncio.ThreadSafeFlag 는 Event 가 지원하지 않는 경우, 즉 인터럽트 핸들러가 asyncio 태스크를 깨워야 하는 경우를 위해 asyncio가 제공하는 신호 프리미티브입니다. 인터럽트는 이벤트 루프의 정상적인 스케줄링 밖에서 발생하므로, asyncio.Event.set() 이 수행하는 관리 작업을 인터럽트 내부에서 안전하게 수행할 수 없습니다.
8.9.1. 별도의 프리미티브가 필요한 이유¶
asyncio.Event.set() 은 루프가 현재 실행 중인 태스크 내부에서 호출되고 있다는 가정 하에 대기 중인 코루틴을 깨우는 관리 작업을 수행합니다. 인터럽트 핸들러는 그 가정을 충족하지 않습니다. 인터럽트는 임의의 두 명령어 사이에서 발생할 수 있어 루프가 진행 중이던 작업을 손상시킬 수 있습니다.
asyncio.ThreadSafeFlag.set() 은 그러한 컨텍스트에서 안전하도록 작성되었습니다. 이 안전성에 대한 대가로 Event 보다 기능 집합이 작습니다. ThreadSafeFlag 는 한 번에 하나의 태스크만 대기할 수 있으며, 대기 중인 태스크가 깨어나면 플래그가 자동으로 재설정됩니다.
8.9.2. 기본 형태¶
전형적인 사용 사례는 GPIO 인터럽트가 버튼 누름 이벤트를 asyncio 태스크에 넘기는 것입니다:
import asyncio
from machine import Pin
flag = asyncio.ThreadSafeFlag()
def on_press(pin):
flag.set()
async def watcher():
button = Pin("P1", Pin.IN, Pin.PULL_UP)
button.irq(handler=on_press, trigger=Pin.IRQ_FALLING)
while True:
await flag.wait()
print("button pressed")
asyncio.run(watcher())
on_press 는 GPIO가 발생할 때마다 인터럽트 컨텍스트에서 실행됩니다. 이것이 하는 일은 flag.set() 를 호출하는 것뿐입니다. asyncio 태스크 watcher 는 루프 안에서 flag.wait() 를 await 합니다. 플래그가 설정될 때마다 태스크가 재개되어 버튼 누름이 요구하는 작업을 수행한 뒤, 다시 대기로 돌아갑니다.
8.9.3. 세 가지 메서드¶
8.9.4. 단일 대기자만 허용¶
한 번에 하나의 태스크만 wait() 에 있을 수 있습니다. 애플리케이션이 원하는 형태는 플래그를 위한 단일 소유자 태스크(보통 인터럽트 핸들러를 설치한 바로 그 태스크)이며, 다른 태스크들은 Event, Lock, 또는 그러한 프리미티브로 보호되는 공유 데이터 구조를 통해 그 소유자와 통신합니다.
버튼이 여러 태스크를 깨워야 할 때, 소유자 태스크가 분산 처리를 수행할 곳입니다. 위의 watcher는 각 누름 이후 some_event.set() 을 호출할 수 있고, some_event 를 기다리던 모든 태스크가 재개됩니다.