21.6.4. 블로킹 회피 메커니즘(Non-blocking Execution)

21.6.4. 블로킹 회피 메커니즘(Non-blocking Execution)

PX4의 Work Queue(워크 큐) 시스템은 픽스호크의 빈약한 메모리와 CPU 자원을 극한까지 끌어올린 혁명적인 스레드 풀(Thread Pool) 아키텍처이다. 하지만 이 강력한 공공 버스(Bus) 시스템은, 승객(모듈) 단 한 명의 이기적인 행동으로 인해 버스 전체가 낭떠러지로 굴러떨어질 수 있는 무시무시한 연대 책임(Group Liability)의 약점을 가지고 있다.

1. 연대 책임의 원리: 협력적 멀티태스킹(Cooperative Multitasking)

과거 독립 태스크(Task) 시절에는 내 모듈이 무한 루프에 빠지거나 10초 동안 멈춰(Blocking) 있더라도, 커널 스케줄러가 강제로 내 목덜미를 잡고 쫓아낸 뒤 다른 모듈을 실행해 주었다. (선점형 스케줄링)

하지만 Work Queue 체제에서는 상황이 다르다. 여러 개의 모듈(CustomApp, mc_pos_control, navigator …)이 단 하나의 스레드(wq_hp_default)라는 버스에 합승한 상태이다.
만약 CustomApp 모듈의 Run() 함수가 실행권을 넘겨받은 뒤, 그 안에서 무언가를 기다리느라 제어권을 1초 동안 반환(return)하지 않는다면 어떻게 될까?

버스 기사(스레드 루프)는 CustomApp이 짐을 다 내릴 때까지 멈춰 서서 하염없이 기다리게 된다. 그 1초 동안, 버스 뒷자리에 앉아 발을 동동 구르던 mc_pos_control(위치 제어기)은 단 한 번도 실행되지 못한 채 기체는 땅바닥으로 곤두박질치고 만다. 이것이 바로 악명 높은 상호 블로킹(Mutual Blocking) 또는 스타베이션(Starvation) 현상이다.

2. 블로킹 없는(Non-blocking) 비행의 철학

이 참사를 막기 위해, PX4 시스템에서 Work Queue 버스에 올라타는 모든 모듈의 Run() 함수는 반드시 아래의 **‘블로킹 회피 제1원칙’**을 목숨처럼 지켜야 한다.

Run() 함수 내부에서는 절대로, 어떠한 이유로든 스레드의 휴식(Sleep)이나 무한정 대기(Blocking Wait)를 유발하는 코드를 작성해서는 안 된다. 모든 연산은 번개처럼 끝내고 즉각 return 하여 버스 기사에게 운전대(Control)를 넘겨라.

이 철칙을 어기는 순간, 당신의 모듈은 악성 종양이 되어 픽스호크 시스템 전체를 마비시키게 된다.

그렇다면 초보 개발자들이 흔히 저지르는 치명적인 블로킹 유발 코드들은 어떤 것들이 있으며, 이를 Non-blocking 설계로 전환하기 위해서는 어떻게 아키텍처 구조를 뜯어고쳐야 할까?
운영체제의 심장을 멈추게 하는 치명적인 파일 입출력(I/O) 및 스핀록(Spinlock)의 공포를 다음 장(21.6.4.1)에서 생생히 해부해 보겠다.