1장 간략히 살펴보는 퀀텀 프로그래밍
퀀텀 프로그래밍(Quantum Programming QP)은 프로그래머가 더욱 쉽게 접근할 수 있는 현대적인 방법을 만들고자 하는 시도다. QP란 간단한 설계패턴과 이디엄(idiom), 구체적 구현, 일반적 기법의 집합으로, 복잡한 툴 없이도 바로 시작해볼 수 있다.
1.1 Ultimate Hook – GUI 애플리케이션 해부
Ultimate Hook이란 차이점을 이용한 프로그래밍이란 뜻이다.
GUI 측면에서 보면, 모든 이벤트는 애플리케이션으로 보내지며 애플리케이션에서 처리되지 않은 이벤트는 시스템으로 넘어온다. 시스템은 모든 이벤트에 반응할 수 있어 이벤트에 응답을 줄 것이다. 즉, 애플리케이션 프로그래머는 표준 시스템 비헤비어와의 차이점만을 코딩하면 된다.
한편 David Harel은 위와 같은 아이디어를 유한상태머신(finite state machine) 형식에 적용했다. 그는 복잡한 반응형 시스템을 표현하는 강력한 방법인 상태챠트(statechart)를 고안해다. 그리고 기존의 기존의 유한상태머신에 비해 상태챠트가 이뤄낸 혁신은 바로 계층형 상태(hierachical state)의 도입이었다.
1.2 더 좋은 프로그래밍 방법 – 계산기
1.2.1 기존 이벤트-액션 패러다임의 단점
버그양산이 필연적이다. 왜냐하면 현재 모드를 검사하려면 복잡한 표현식을 검사해야 하고, 모드간 전이를 수행하려면 많은 변수를 고쳐야 하므로 쉽게 일관성을 잃게 된다.
1.2.2 계산기 상태차트

OPERATOR C를 처리하는 것이 ULTIMATE-HOOK를 이용해서 한 번만 코딩할 수 있도록 되었다. 이것은 계층적 유한상태차트의 속성에서 기인한다.
위 상태차트를 나타내는 헤더파일에서 static Calc* instance(); 정적 함수는 오직 하나의 인스턴스만을 사용하기 위해서 선언된 것이다. Singleleton 접근 메소드라고 한다.
1.2.3 윈도우와의 통합
윈도우즈 프로그래밍을 작성시 QF와 통합하는 예를 보인다.
1.2.4 상태 핸들러 메소드
상태 핸들러 메소드는 애플리케이션의 실제 작업을 수행한다. 상태핸들러는 Calc 클래스의 멤버이기 때문에 모든 속성에 접근할 수 있다. 상태 핸들러는 이벤트 인스턴스의 상수 포인터(QEvent cont *)를 받아들인 뒤, 이 인스턴스가 이벤트를 처리했으면 0을 반환하고, 그렇지 않으면 상위상태, 더 정확히는 상위상태 핸드러 메소드의 포인터를 반환한다.
상태 핸들러라는 것은 상태를 핸들링하는 메소드를 말한다. 왜 핸들러인가? 상태를 변경해 주는 역할을 하기 때문이다. (나중에 qm.exe를 다루다 보면 housekeeping code를 다이어그램이 만들어 준다.)
1.3 객체지향과의 유사성
1.3.1 상태계층과 클래스
상태에서 is in(is-in-a-state) 개념의 일반화는 클래스에서 is a(is-a-kind-of) 개념의 일반화에 해당한다.
1.3.2 상태 진입/탈출과 클래스 초기화/종료
진입액션은 생성자, 탈출액션은 소멸자와 비슷하다.
1.3.3 차이점을 이용한 프로그래밍
차이점을 이용한 프로그래밍은 비헤이버 상속을 통한 재사용과 같다.
1.3.4 기초 메타패턴으로서의 비헤비어 상속
(기초를 기본으로 번역하는 것이 맞을 듯 하다.)
OOP는 추상화, 상속, 다형성의 세가지 기본개념을 일관적으로 사용하는 것이라 할 수 있다. 이러한 개념이 메타패턴이라고 한다. QP는 비헤비어 상속이라는 메타패턴을 도입했다.
1.3.5 상태패턴/State Pattern
QP는 비헤비어 상속 메타패턴을 구체적으로 구현해 놓음으로써, 구체적이고 실행 가능한 코드의 형식으로 기존의 그래픽 상태차트 표기법만을 쓸 때보다 훨씬 더 정확히 상태패턴을 서술할 수 있게 해준다.
QP에서 상태패턴은 직교 컴포넌트보다는 비헤비어 상속이라는 핵심개념에 초점을 맞추고 있으므로 다른 분야에서 제시된 솔루션의 훌륭한 대안이 될 수 있다.
1.3.6 상태 모델의 리팩토링
주저리 말은 많으나 코드의 변경이 쉽다는 말이다.
1.3.7 객체지향 프로그래밍을 넘어
OOP는 프레임워크로 진화하였으나 QP는 이와 반대의 길을 걸었다. OOP가 클래스 메소드의 내부구현을 절차식 기법에 의존하나 비헤비어 상속가 OO 개념의 결합은 많은 OO 방식이 클래스 안으로까지 확장되어 적용되었다.
1.4 퀀텀 프로그래밍과 양자역학
OOP는 양자모델을 아주 충실히 따르고 있다. 1부에서는 퀀텀 시스템의 첫번째 특성인 이산적이고 상태에 기반한 비헤비어를 다룬다. 그리고 2부에서는 퀀텀 유사성의 두번째 특성인 상호작용을 다룬다. QP의 기본 단위는, 동시 활성화되는 계층형 상태머신(활성화 객체)이다. 이들 소프트웨어 머신은 다양한 이벤트 인스턴스를 비동기적으로 교환함으로써 다른 소프트웨어 머신과 상호작용할 수 있다.
(다른 상태머신과 상호작용을 할 수 있다는 말이다.)
2. 상태차트
2장에서는 상태머신과 상태 모델링의 역할이라는 관점에서, UML 상태차트를 진부하지 않은 새로운 접근법으로 소개한다. 즉, 기본용어를 습득하고, 표기법을 이해하며, 용법을 확실히 함으로써 빠른 시간에 기초를 확립하도록 하는 것이다.
2.1 유한 상태머신의 핵심
시스테이 각 시점에서 다르게 동작하고, 시스템의 비헤비어를 상태라는 유한의 중복되지 않은 부분으로 나눌 수 있을 때, 시스템은 상태 비헤비어를 보인다. 특히 유한한 개수의 상태를 가지는 것을 유한상태머신(FSM:Finite State Machine)이라고 한다.
2.1.1 상태 / States
상태란 시스템이 작동하는 동안의 상황이나 조건을 뜻한다.
2.1.2 확장상태 / Extended States
시스템의 완전한 조건(확장상태 라고 함)은 질적인 면인 상태와 양적인 면인 확장상태변수(extended state variable)를 합한 것이다. 상태머신에는 확장상태머신(extended state machine)이라는 메모리가 추가된다. 키보드를 100,000 번 누르면 키보드가 망가진다는 상태전환을 체크할 때 사용한다.
2.1.3 가드 / Guards
확장상태변수는 가드 또는 가드조건이라는 불린(Boolean) 값을 동적으로 만들어 낸다. 즉, 가드조건은 상태전이 같은 어떤 작업을 허용하거나 금지하는 방식을 통해 상태머신의 비헤비어에 영향을 미친다. 그러나, 과도한 가드의 사용은 구조적 붕괴를 가져 올 수 있으니 남용하면 안된다.
2.1.4 이벤트 / Event
시스템에 있어 중요한 사건이 어떤 시간과 공간에서 발생하는 것을 뜻한다. 엄밀히 얘기하면, UML 명세에서 ‘이벤트’란 용어는 사건의 유형(type)을 뜻하며, 사건의 구체적 인스턴스를 뜻하는 것은 아니다.
이벤트는 매개변수를 둘 수 있다. 매개변수는 이벤트 인스턴스가 어떤 흥미로운 사건의 발생뿐 아니라 이 사건에 관한 양적 정보도 전달할 수 있게 한다. 키보드를 누르면 키보드의 스캔코드가 매개변수로 이용된다.
이벤트 인스턴스는 자신을 만들어낸 순간적인 사건보다 오래 남아 있므며, 이 사건을 하나 이상의 상태머신에 전달한다.
이벤트 인스턴스가 일단 만들어지면 3단계로 이뤄진 처리과정을 거친다. 먼저 허가된 이벤트 인스턴스는 절달되어 처리를 기다리게 된다. 다시 말해 ①이벤트 대기열에 위치하는 것이다. 이후 이벤트 인스턴스는 ②상태머신에 보내지는데, 바로 이때 이벤트가 현재 이벤트가 된다. 끝으로 ③상태머신이 처리를 마치면 이벤트 인스턴스는 소멸된다. 일단 처리된 이벤트 인스턴스는 다시 처리되지 않는다.
2.1.5 액션과 전이 / Actions and Transitions
이벤트 인스턴스가 넘어오면 상태머신은 액션을 수행함으로써 응답한다. 이 액션에는 변수값의 수정, I/O 수ㅐㅇ 함수, 함수 호출, 다른 이벤트 인스턴스 생성, 다른 상태로의 전이 등이 있다. 특히 다른 상태로의 전이를 일르키는 이벤트를 촉발이벤트 또는 트리거라 한다.
확장상태머신에서는 전이에 가드가 있을 수 있다. 그런데 하나의 이벤트에 여러 전이를 가지는 경우에 있어서 가드의 평가순서가 문제가 된다. 이것을 side effect라고 하는데 이러한 효과를 없도록 하는것이 중요하다.
2.1.6 Mealy 오토마타와 Moore 오토마타
Moore 오토마타의 결과는 현재 상태에만 영향을 받지만, Mealy 오토마타의 결과는 현재 상태와 현재 입력의 영향을 받는다.
2.1.7 실행모델 – Run To Completion 스텝
액션을 실행하면, 실행이 끝나기까지 언제나 어느 정도의 시간이 걸린다. 따라서 상태머신은 두 가지 모드를 취사선택할 수 있다. 2개의 모드는 idle(다음 이벤트를 기다림)과 busy(이벤트에 응답 중)로 나뉜다.
선점형 처리의 반댓말은 RTC 처리이다. RTC 모델에서는 시스템이 이산적이고 분할할 수 없는 RTC 단계에 따라 이벤트를 처리한다. 높은 우선순위의 이벤트가 다른 이벤트의 처리에 간섭할 수 없으며, 따라서 내부의 동시처리 문제가 완전히 사라진다. 이 모델은 Mealy 오토마타에서 발생하는 적절히 정의되지 않은 상태 문제도 해결한다. 이벤트 처리 중에 시스템은 반응할 수 없으므로, 이 시점에서 적절히 정의되지 않은 상태는 사실상 중요치 않다.
2.1.8 상태전이 다이어그램 / State Transistion Diagram
FSM은 상태전이 다이어그램(state transistion diagram)이라는 형식을 이용, 시각적으로 나타낼 수 있다. 이 다이어그램은 상태를 나타내는 노드와 전이를 나타내는 연결선으로 구성된 도표다.

2.2 UML 상태차트의 기초
UML 상태차트는 Mealy 오토마타와 Moore 오토마타의 특징 모두를 가지고 있는 확장상태머신이다. Mealy 오토마타에서와 마찬가지로, 상태차트에서 액션은 대개 시스템의 상태와 촉발 이벤트 모두에게 영향을 받는다. 또 상태차트는 진입액션과 탈출액션을 제공할 수도 있는데, 이것은 Moore 오토마타에서 처럼 전이가 아니라 상태에 연결된다.
(QP.exe로 그림을 그리다 보면 알 수 있다. 진입/탈출 액션이 상태내부에 존재한다.)
2.2.1 계층형 상태 / Hierarchical States
앞에서 본 상태차트는 계층형으로 중첩된 상태가 아니다. 계층형으로 중첩된 상태를 우리는 계층형 상태머신(HSM:Hierarchically Nested State)이라고 부른다.

2.2.2 비헤비어 상속 / Behavioral Inheritance
계층형 상태는 상태와 전이의 개수를 줄인 것이다. OOP에서의 상속의 모든 기본 특성이 중첩상태에도 똑같이 적용된다. ‘클래스’를 ‘상태’로 이름만 바꾸어도 그 의미가 동일하다. 즉, 중첩 상태에서는 is(is-a-kind-of) 관계를 is in(is-in-a-state) 관계로 바꾸기만 하면 된다는 의미이다. 상태중첩은 하위상태가 상위상태를 비롯한 조상상태로부터 상태 비헤비어를 상속하도록 한다. 이 때문에 비헤비어 상속(behavioral inheritance)이라고 하는 것이다. 비헤비어 상속은 QP에 고유한 것으로, 하위상태와 상위상태의 관계를 나타내는 것이다.
기존 클래스 구조에서 LSP는 하위클래스가 자유롭게 상위클래스를 대체할 수 있어야 함을 뜻한다. LSP를 상태에 적용하면, 하위상태의 비헤비어는 상위상태와 일고나성을 가져야 한다는 뜻이다.
상속의 개념은 소프트웨어 구축의 기초다. 클래스 상속은 더 나은 소프트웨어 구성과 코드 재사용에 있어 필수적인 것으로, OOP의 초석을 이룬다. 마찬가지로 비헤비어 상속은 HSM의 효율적 활용과 비헤비어 재사용에 필수적인 것으로, QP의 초석이 된다.
2.2.3 직교영역 / Orthogonal Region
시스템의 비헤비어를 독립적으로 활성화 되는 여러 부분으로 나눌 경우, 직교영역(독립적인 관계)은 상태의 수가 폭발적으로 증가하는 문제를 해결한다. 보통 독립된 관계는 외적(각 상태의 갯수를 곱해줌)하나, 직교영역은 그림 2.3처럼 각 상태의 합만을 취할 수 있게 만들어 준다.

그러나 대부분의 실제 상황에서 직교 영역은 근사적으로 직교할 뿐이며 완전히 독립적이지는 않은 경우가 많다. 따라서 UML 상태차트는 직교영역에 비헤이버를 교환하고 동기화 하는 많은 방법을 제공한다. 이 매커니즘 집합에서 가장 중요한 것은, 이벤트 인스턴스를 통해 직교영역끼리 상호작용이 가능하다는 것이다.
2.2.4 진액액션과 탈출액션 / Entry Action and Exit Action
UML 상태차트에서 모든 상태는 진입액션과 탈출액션을 둘 수 있다. 진입액션은 상태로 들어갈 때 실행되는 액션이고, 탈출액션은 상태를 빠져나올 때 실행되는액션이다. 진입액션과 탈출액션은 전이가 아니라 상태에 연결된다.
진입액션과 탈출액션은 생성자의 호출과 소멸자의 사용과 매우 유사하다. 그러나, 상태차트에서 전이가 발생하면, 이러한 전이는 객체의 완전한 소멸과 재생성(클래스는 부모클래의 완전한 소멸과 새로운 파생클래스의 생성을 필요로 한다.)을 요하지는 않는다. 그냥 탈출액션을 수행 한 뒤 다른 상태의 진입액션을 수행하면 된다.

2.2.5 전이 실행 시퀀스 / Transistion Execution Sequence
내부전이를 제외한 모든 상태전이는, 상태차트가 소스(source) 상태형상을 빠져 나와 타겟(target) 상태형상으로 들어가도록 한다.
1단계 : 소스 상태형상의 탈출액션
2단계 : 전이와 연결된 액션
3단계 : 타겟 상태형상의 진입액션
UML 명세에서 언급한 바에 따르면, 1단계에서 현재 활성상태에 중첩된 모든 상태(소스상태의 직접 하위상태이거나 추이적 하위상태)를 빠져나와야 하지만, 소스상태와 타겟상태의 최소공통조상(LCA: Least Common Ancestor)을 빠져 나와서는 안된다. 즉, 탈출액션의 실행순서는 항상 가장 낮은 수준의 중첩상태부터 LCA까지다.
진입액선의 실행은 탈출액션이 끝난 LCA부터 시작해 타겟상태까지계층을 따라 내려간다. 타겟상태가 복합상태일 경우에는 다시 기본전이나 이력 매커니즘을 통해 그 하위머신에 들어간다. 이력의사상태(history pseudostate)는 2.2.7절에서 알아본다.
2.2.6 내부전이 / Internal Transistion
대게 이벤트는 실행할 내부 액션 몇 가지만을 발생시키고, 상태의 변화(상태전이)를 유발하지는 않는다. 이 경우 실행되는 액션은 모두 내부전이(internal transition)라고 한다. 내부전이는 촉발 이벤트와 액션으로 구성된다.
자기전이는 내부전이에 진입액션과 탈출액션을 더해준 것이다. 즉, 일반적인 전이 시퀀스를 따라서 자기 자신의 상태로 돌아온다.
2.2.7 의사상태 / Pseudostate
상태차트에 부가되는 특정한 기능을 표시하기 위하여 노드라는 것이 있다. 상태머신 그래프의 여러 노드를 아우르는 추상적 개념을 의사상태라고 한다.
초기 의사상태(검은 점으로 표시)
얕은 이력 의사상태(H)
깊은 이력 의사상태(H*)
결합 의사상태
분기 의사상태
연결 의사상태
선택 의사상태 (빈 원이나 마름모로 표시)
2.2.8 정교한 이벤트 처리
UML 명세는 네 종류의이벤트를 정의하고 있으며, 각 이벤트는 서로 다르게 표기된다.
SignalEvent : 특정한 비동기 신호를 받았음을 나타낸다. 시그넘이름(parameter, … )
TimeEvent : 지정한 제한시간의 말료를 모델링한다. after 시간길이
CallEvent : 지정한 작업을 동기적으로 호출한다. 작업이름(parameter, …)
ChangeEvent : 명시적 불린식이 참일 때 발생하는 이벤트를 모델링한다. when Boolean식
Polymorphic Event Triggering : 주어진 신호 이벤트가 촉발한 전이가 하위 이벤트를 촉발하는 것을 말한다.
Differing Event : 상태는 지연 이벤트의 목록을 지정할 수 있다. 넘겨받은 이벤트가 그 상태의 지연 이벤트 목록에 있을 때 이벤트는 처리되지 ㅇ낳으며, 해당 이벤트를 지연시키지 않을 상태에 도달할 때까지 지연 이벤트 대기열에 남게 된다.
2.2.9 의미와 표기법
상태차트의 많은 요소와 의미가 도식적 표기법에 심각하게 치우쳐있다. 그러나 QP에서는 상태차트를 C나 C++로 구현할 때 여러분은 실행순서를 마음대로 제어할 수 있다.
2.2.10 상태차트와 프로우차트
프로우차트는 처리단계를 표시한 것이며 상태차트는 시스템 전체 비헤비어의 제약조건을 지정하는데 있어 효과적인 방법이다.
2.2.11 상태차트와 코드자동생성
상태차트 기발 툴은 소위 말하는 하우스키핑 코드를 스스로 생성할 수 있을 뿐이다. 모델링하는 사람이 액션이나 가드의 표현식처럼 특정 애플리케이션 전용 코드를 직접 입력해야 한다.
2.3 상태모델 예제
퀀텀 계산기의 상태모델을 알아본다.
2.3.1 퀀텀 계산기

(1-1단계) 시스템의 주요 기능을 구현한다. 계산기는 “operand1 operator operand2 =”을 기본 기능이라 할 수 있다. 상태머신은 operand1 state에서 시작하는데, 이 상태의 기능은 사용자가 올바른 피연산자를 입력하게 하는 것이다. 이 상태가 목적을 달성하려면 내부 하위머신이 필요하겠지만 지금은 무시하기로 하자. operand1을 빠져나오는 전이의 기준은 operator(+,-,*,/)이다. 그리고 나면 상태차트는 opEntered 상태로 들어가는데, 여기서 계산기는 둘째 피연산자의 입력을 기다린다. 사용자가 숫자(0~9)나 소수점을 클릭하면 상태머신은 operand2 상태로 전이하는데, 이 상태는 operand1과 비슷하다. 끝으로 사용자가 ‘=’을 클릭하면 계산기는 계산을 수행해서 결과를 출력한다. 그리고 나서 다시 operand1 상태로 다시 전이해 다른 계산을 준비한다.
(1-2단계) 그런데 그림2.6의 (a)의 간단한 상태모델에는 중요한 문제점이 있다. 사용자가 마지막 단계에서 ‘=’를 클릭하면 결과값을 출력해주는 result 상태가 없기 때문이다. 이 result 상태에서는 세 가지 일이 일어날 수 있는데 (1) 사용자가 연산자 버튼을 눌러 계산결과를 새 계산의 첫번째 피연산자로 사용하거나, (2) 취소(C)를 눌러 완전히 새로운 계산을 시작하거나, (3) 숫자나 소수점을 눌러 첫번째 피연산자의 입력을 시작하는 것이다.

(2-1단계) (1-2단계)의 문제점은 result 상태에서만 C를 입력받는다는 것이다. 그러나 사용자는 언제라도 계산을 취소하고 다시 시작할 수 있을 것이라 기대한다. 그래서 C를 모든 상태에서 가능하도록 변경하였다.
(2-2단계) 그러나 이보다 더 나은 해결책은 공통 전이를 더 높은 수준의 상태인 calc로 쪼개로 비헤비어 상속을 통해 모든 하위상태가 Cancel 전이를 재사용하게 하는 것이다. 이것은 그림 2.7 (b)와 같이 자기전이로 구현했다. 이 방법은 더 많은 재사용을 가능하게 한다. 자기전이는 상태를 빠져나와 다시 들어가도록 하기 때문이다. 따라서 초기화를 재사용해서 취소액션이 수행되게 한다.

(3-1단계) operand1과 operand2 상태에서는 소수를 분석하는 하위머신이 필요하다. zero 하위상태는 사용자가 0을 클릭하면 들어가는 곳이다. 이 상태이 기능은 사용자가 눌러버릴지도 모를 0을 무시, 계산기가 1개의 0만을 출력하도록 하는 것이다. int 하위상태의 기능은 정수부를 분석하는 것이다. 사용자가 1~9를 누르면 외부나 zero 하위상태에서 이 상태로 들어간다. 끝으로 frac 하위 상태는 소수브를 분석한다. 사용자가 소수점을 누르면 2개의 하위 상태나 외부에서 이 상태로 들어간다.

(4-1단계) ‘-‘가 뺄셈이 아니 음의 기호를 나타내는 문맥은 딱 두가지인데 (1) opEntered 상태, (2) 새로운 계산의 맨 처음인 경우다. (1)의 경우, negated2라는 하나의 상태만 더 말들면 되고 연산자가 MINUS일 때 이 상태에 들어가게 된다(가드의 사용법을 생각해보라).
(4-2단계) (2)번째 방법은 약간 까다롭다. 새 계산의 시작을 어떤 상황으로 설정할 것인지 결정하기가 매우 애매하기 때문이다. 여기서는 애플리케이션을 막 시작한 경우나 사용자가 취소를 클릭한 경우라고 할 것이며, 계산기가 이전 계산결과를 출력한 직후는 제외한다.

(5-1단계) 적당한 문맥에 입력취소 전이를 추가하고, final 상태로의 전이를 추가해서 애플리케이션을 끝낼 수 있도록 한다.
3장 표준 상태머신 구현
- 이벤트를 어떻게 표현할 것인가? 매개변수가 있는 이벤트의 경우는 또 어떻게 할 것인가?
- 상태를 어떻게 표현할 것인가?
- 전이를 어떻게 표현할 것인가?
- 상태머신에 어떻게 이벤트를 넘겨줄 것인가?
3.1 상태머신 인터페이스
표준 상태머신 인터페이스는 없으나 본 교재에서는 3개의 메소드로 이루어진 상태머신 인터페이스를 제안한다. 최상위 초기전이를 수행하는 init(), 상태머신에 이벤트를 전달하는 dispatch(), 임의의 상태전이를 수행하는 tran()이 바로 그것이다.

*.c 파일을 읽어 들여서 코멘트의 갯수를 저장하는 코멘트 파서 하네스이다.
/////////////////////////////////////////////////////////////////////
// QP C-comment parser test harness
// Copyright (c) 2002 Miro Samek, Palo Alto, CA.
// All Rights Reserved.
/////////////////////////////////////////////////////////////////////
#include <stdlib.h>
#include <stdio.h>
#include "qassert.h"
DEFINE_THIS_FILE;
extern "C" void onAssert__(char const *file, unsigned line) {
fprintf(stderr, "Assertion failed in %s, line %d", file, line);
exit(-1);
}
#include "cparser1.h"
static CParser1 cparser;
int main(int argc, char *argv[]) {
FILE *f;
long n = 0;
int ch;
if (argc < 2) {
printf("usage: %s <c-file>\n", argv[0]);
return -1;
}
f = fopen(argv[1], "r");
if (!f) {
printf("file %s cannot be opened for reading\n", argv[1]);
return -1;
}
n = 0;
cparser.init();
while ((ch = fgetc(f)) != EOF) {
int sig;
switch (ch) {
case '/': sig = SLASH_SIG; break;
case '*': sig = STAR_SIG; break;
default: sig = CHAR_SIG; break;
}
cparser.dispatch(sig);
++n;
}
fclose(f);
printf("file %s contains %ld comment characters, "
"%ld total.\n",
argv[1], cparser.getCommentCtr(), n);
return 0;
}
3.2 중첩 switch 문
상태머신을 구현하는데 있어 가장 널리 사용되는 기법은 아마도 중첩 switch 문일 것이다. 여기서는 상태변수를 switch문의 첫번째 수준 식별자로, 이벤트 신호를 두번째 수준 식별자로 사용한다.
enum Signal
{
CHAR_SIG,
STAR_SIG,
SLASH_SIG
};
enum State
{
CODE,
SLASH,
COMMENT,
STAR_SIG
};
class Cparser1
{
public:
void init()
{
myCommentCtr = 0;
tran(CODE);
}
void dispatch(unsigned const sig);
void tran(State target) { myState = target; }
long getCommentCtr() const { return myCommentCtr; }
private:
State myState;
long myCommentCtr;
};
void CParser1::dispatch(unsigned const sig)
{
switch (myState)
{
case CODE:
switch (sig)
{
case SLASH_SIG;
tran(SLASH);
break;
}
case SLASH:
switch (sig)
{
case STAR_SIG:
myCommentCtr += 2;
tran(COMMENT);
break;
case CHAR_SIG:
case SLAH_SIG:
tran(CODE);
break;
}
// .....
}
}
신호는 보통 열거형으로 나타내며, 상태 역시 마찬가지다. myState 상태변수는 Cparser1 클래스내에 위치한다. 각 인스턴스는 자신의 현재 상태가 무엇인지 알고 있어야 하기 때문이다.
상태머신은 init()의 호출을 통한 초기전이로 시작한다. 상태머신의 핵심은 dispatch() 이벤트 핸들러 메소드인데 클라이언트 코드는 이메소드를 각 RTC 단계마다 한번씩 호출한다. 상태전이는 상태변수에 값을 재할당함으로써 이뤄진다.
상태머신은 싱글톤(singleton)인 경우가 많다(싱크로톤이란 시스템에서 오로지 1개의 인스턴스만 존재하는 것을 뜻한다). 이 경우 상태머신은 클래스가 아니라 모듈로 코딩할 수 있으며, 하나뿐인 상태변수 인스턴스는 이벤트 핸들러에 하드코딩할 수 있다.
3.3. 상태 테이블

이 테이블에서는 위쪽에 신호(트리거)를, 왼쪽에 상태를 열거하고 있다. 셀의 내용은 {액션, 다음상태} 쌍으로 표현한 것이다. 상태이동을 발생시키는 칸에 target 상태이름을 적는다. 아루런 일도 하지 앟고 상태만 변경하는 경우 doNothing()을 기입하고 myCommentCtr++는 a1(), myCommentCtr+=2는 a2()에서 담당한다고 가정하고 해당 칸에 기입한다. 빈 셀은 정의되지 ㅇ낳은 신호-상태 조합을 나타낸다. 이러한 트리거를 처리하는 공통 정책은 상태를 박꾸지 ㅇ낳고 그냥 무시하는 것이다. 물론 여러분은 다른 정책을 채택할 수도 있다. 예를 들어 이벤트에서는 error() 액션을 수행할 수도 있다.
위 테이블을 C++코드로 만들어 놓으면, 재사용이 가능한 공용 이벤트 처리부(클래스로 정의)와 애플리케이션 전용 부분으로 나뉘어 있다. 클래스는 물리적인 전이부를 가지지 않으며 이들은 사용자에게 일임된다. 액션 다음상태 쌍은 struct로 정의되어 있다.
이벤트와 상태는 모두 무부호 정수로 표현되며, 대개 클라이언트가 이들을 열거한다. dispatch()에소드는 3단계로 싱행된다. (1)실행할 전이를 상태 테이블에서 찾고, (2)액션을 실행 한 뒤, (3) 상태를 바꾼다.
상태 테이블을 이용한 코딩패턴은 복잡한 초기화가 필요하며 개선 및 변경을 어렵게 만든다. 장점은 액션과 상태전이가 고정되어 있어 ROM에 집어 넣고 사용할 수 있다는 점이 장점이다.
3.4 상태 설계패턴
상태머신을 구현하는데 쓰이는 객체지향 접근법을 State 설계패턴(State design pattern)이라고 한다.

위 패턴은 위임(deligation)과 다형성에 기반을 두고 있다. 구상 상태는 이벤트 처리의 공통 인터페이스를 정의하는 추상 상태 클래스의 하위클래스로 표현되고 각 이벤트는 가상 메소드에 연결된다. 컨텍스트 클래스는 모든 이벤트를 현재 상태객체(myState 속성에 의해서 결정 됨)에 위임한다. 상태 전이는 명시적이며, myState 포인터를 재할당함으로써 수행된다. 새로운 이벤트를 추가하려면 추상 상태 클래스에 새로운 메소드를 추가해야 하고, 새로운 상태를 추가하려면 이 클래스의 하위 클래스를 파생해야 한다.
표준 State 설계패턴은 RTC 단계를 수행할 때 dspatch() 메소드를 쓰지 않는다. 대신 모든 신호 이벤트에 전용 이벤트 핸들러 메소드를 제공한다. 그러나 이 패턴은 그림 3.5와 같이 이 상태 클래스의 모든 이벤트 핸들러를 하나의 공용 상태 핸드러 dispatch()에 조합하는 방식으로 수정(간략화)할 수 있다. 이렇게 한면 추상 상태클래스는 공용이 되고, dispatch() 메소드는 공용 상태 핸들러가 된다. 그러나 이벤트의 유형을 통한 이벤트의 식벽은 구상상태 하위클래스의 dispatch() 메소드 안에서 이뤄져야 한다.
3.5 최적 FSM 구현

이 구현은 충첩 switch 문, 상태 테이블, Simplified State 설계패턴의 요소들을 조합했을 뿐 아니라, 독창적인 개념도 몇 가지 포함시켰다. 이 설계의 핵심은Fsm 클래스이다. 이 클래스는 Simplified State 설계패턴에서의 컨텍스트 클래스, 상태 테이블 패턴에서의 이벤트 처리기라는 두가지 역할을 소화한다.
- 간단하다.
- 상태전용 비헤비어를 분할해서 여러 개의 상태 핸들러 메소드로 나눈다. 이들 메소드는 알맞은 상세도를 갖는다. 액션 메소드처럼 너무 작게 나눠져 있지도 않고, 하나로 통합된 이벤트 핸들러처럼 덩치가 크지도 않다.
- myState 포인터에 재할당하는 기법으로, 상태전이를 효율적으로 한다.
- 상태를 열거할 필요가 없으며, 이벤트만 열거하면 된다.
3.6 상태머신과 C++ 예외처리
외부 예외처리 기법을 이요하지 말고 상태머신이 제공하는 독립적인 방식으로 예외를 처리해야 한다. 반응형 시스템에 연결된 상태머신은 폴트(fault) 상태를 포함한 시스템의 모든 조건을 표현할 수 있다. 액션은 예외를 던지지 ㅇ낳고 예외 비벤트를 만들어내야 한다. 그리고 예외 이벤트는 상태에 기반을 둔 예외처리를 촉발한다.
3.7 멤버 함수에 대한 포인터의 역할
멤버 함수의 포인터를 이용해서 상태를 변경하는 것이 얼마나 좋은 방법인가에 대한 설명을 담고 있다.
3.8 가드, 연결점, 선택점 구현
가드는 if(guard()) { action1(); } elsef if (guard2()) {action 2(); }와 같은 if-else 문을 이용해서 구현할 수 있다.
3.9 진입액션과 탈출액션의 구현
진입액션과 탈출액션을 구현한느 한가지 방법은, 예약된 신호(예를 들어 ENTRY_SIG와 EXIT_SIG)를 상태머신에 전달하는 것이다. tran() 메소드는 EXIT_SIG를 현재 상태(전이 소스)에 보낸 뒤, 전이 타겟에 ENTRY_SIG 신호를 보낼 수 있다.
Void Fsm::tran (FsmState target) {
(this->*myState) (EXIT_SIG);
myState=target;
(this->*myState) (ENTRY_SIG);
}
3.10 상태계층 처리
HSM은 FSM 보다 전이사슬을 처리하는 것이 어렵다. (1)프로그래머가 전이 사슬을 코딩하는 수고를 덜어주는 중량기법과 (2)프로그래머가 명시적으로 전이 사슬을 하드코딩해야 하는 경량기법이 있다. 유감스럽게도 치밀하고 효율적으로 직접 HSM을 구현하는 것은, 유연하지 못하고 오류 발생의 경향이 짙은 범주에 속하는 기법을 통해서만 가능하다는 편견이 생길 수 있다. 4장에서는 이런 방식을 고집할 필요가 없음을 설명할 것이다. 여러분은 전이 사슬을 처리해주는 경량 기법으로 HSM을 구현할 수 있다.
4장 비헤비어 상속 구현
4장에서는 비헤비어 상속의 실제 구현방법을 HSM 구현 형식으로 설명한다. 비헤비어 상속의 개념은 HSM에서 하위상태와 상위상태의 관계다.
이 책의 HSM 구현에서는 UML 상태머신 패키지에 명기된 모든 기능을 다루지 않고, 다음과 같은 필수 요소 몇 가지만을 다룬다.
- 비헤비어 상속을 완벽하게 지원하는 계층형 상태
- 상태 진입액션과 탈출액션을 통한 확실한 초기화와 제거
- 클래스 상속을 통한 상태 모델의 특수화 지원
4장이 뼈대를 세우는 것이라면 5장은 이러한 비헤비어 상속 구현을 토대로, (1)이벤트 지연, (2)직교영역, (3)이력으로의 전이 상태패턴으로 구현하는 방법을 공부한다. 그리고 6장에서는 전체 상태머신의 상속을 통한 상태모델의 재사용을 알아본다.
4.1 구조
최적 FSM 설계와 마찬가지로, QState(quantum state) 상태는 QHSM 클래스의 멤버 함수에 해나 포인터로 표현된다. QHsm 클래스는 myState 속성을 이용, 활성상태를 추적한다. 그리고 또다른 속성인 mySource를 사용해 상태전이가 일어나고 있는 동안 전이의 소스를 추적한다(HSM에서는 전이를 상위상태로부터 상속받을 경우에는, 그 전이의 소스가 활성상태와 같지는 않다).
4.1.1 이벤트
최적 FSM 접근법은 이벤트의 일관된 표현을 요구한다. 여기서 이벤트를 핸들러 메소드에 넘겨주는 방법은 딱 두 가지다.
- 신호와 공용 이벤트 매개변수를 따로 넘겨주는 방법
- 이벤트 객체로 묶는 방법(본 교재)
이벤트 인스턴스는 주로 신호와 이벤트 매개변수를 함께 묶어 넘겨주는 포장지 역할을 한다. 이러한 역할을 하기 위해서는 객체를 초기화 목록을 이용해 초기화해서 사용하는 것이 편하다. 그래서 클래스는 반드시 집합 클래스여야 한다. 그래서 퀀텀 이벤트 클래스 QEvent는 struct로 선언했다.
그리고 파생된 이벤트 클래스에 생성자나 가상함수를 도입하는 것은 피해야 한다.
4.1.2 상태
그림 4.1에 있는 비헤비어 상속 메타패턴의 구조에는 State 클래스가 없다. 만일 상태를 객체로 표현하면 심각한 문제가 발생하기 때문이다. 객체는 저장과 초기화를 필요로 하는데, 이는 클라이언트에게 아주 불편하기 때문이다.
그러나 상태 핸들러 메소드는 상위상태를 반환함으로써 꼭 필요한 구조적 연결고리와 비헤이버를 제공할 수 있다.
4.1.3 진입/탈출액션과 초기전이

4.1.4 상태전이
Q_TRAN() 매크로는 소스 상태 형상에서의 탈출액션이 실행된 직후 타겟 상태 형상으로의 진입액션이 실행된다. 이 시퀀스에는 전이와 연결된 액션이 포함돼있지 않지만, Q_TRAN() 앞에 정의하면 상태의 전이에 앞서 실행할 수도 있고, Q_TRAN() 뒤에 정의 하면 상태가 전이된 뒤 실행할 수도 있다. 이것의 의미는 Q_TRAN() 매크로가 원자와 같은 상태전이를 숭해하므로 다른 액션에서 이를 중단할 수 없다는 것이다.
4.1.5 최상위상태와 초기 의사상태
QHsm 클래스 구현은 최상위상태를 완전히 구현하고 있으며, 클라이언트는 이를 재정의할 수 없다(QHsm::top() 상태 핸들러는 일부러 가상으로 만들지 않았다).

최상위 상태에서 유일하게 설정 가능한 부분은 초기전이다. 클라이언트는 모든 상태머신에게 반드시 의사상태 핸들러를 정의해야 한다. QHsm 생성자는 초기 의사상태 핸들러를 인자로 요구함으로써 이 조건을 만족시킨다.
4.2 예제

4.2.1 신호 열거와 QHsm 하위클래스 파생

4.2.2 상태 핸들러 메소드 정의
모든 상태 핸들러의 마지막 단계에서는 해당 상태 핸들러를 호출자에게 반환하여 상위 상태를 지정한다.
4.2.3 이벤트의 초기화와 전달

이벤트는 structure로 선언했음을 기억하자. 그리고 상태머신의 인스턴스화(1행), QHsm::init()를 통한 초기전이 촉발(9행), 그리고 QHsm::dispatch()를 통한 이벤트의 전달(17행)이다.
4.2.4 테스트 실행
a를 누르면 아무것도 표시하지 않는 이유를 모르겠다. h를 누르면 가드조건을 수행하는 것은 알겠다.
4.3 휴리스틱과 이디엄
4.3.1 상태머신 코드 구조
명료한 코딩을 하려면, 상태차트 다이어그램에서 나타난 그대로 모든 상태머신 요소를 직접적, 명시적으로 상태 핸들러에 집어넣어야 한다. 코드를 아무렇게나 쪼개지 말고 상태차트의 요소로 분할해야 한다. 즉, 액션, 가드, 연결점, 선택점으로 분할해야 한다는 얘기다.
4.3.2 올바른 신호 상세도(signal granularity) 선택
신호의 구분을 잘 해라. 신호등의 신호가 10개라면? 장시간정지, 잠시정지, 걷기, 빠르게, 열라빠르게 하는 것 보다는 3색 신호등으로 주고 점멸신호만 주면 충분하다.
4.3.3 UML 호환 HSM
간단한 UML 비호환 상태 차트를 만들것을 추천한다.
4.4 이벤트 처리기
이제까지는 상태차트 구현에서 비헤비어 상속 메타패턴을 성공적으로 초기화할 수 있었다. 이제부터는 QHsm 클래스의 내부 동작 방식을 이해하면 더 자신감을 가지고 효과적으로 코딩을 할 수 있다. 이 절에서는 액션과 전이를 실행하는 이벤트 처리기를 살펴보자.
4.4.1 상태머신 초기화: init() 메소드
QHsm::init()의 임무는 초기전이를 촉발하고 최상위상태의 하위머신에 계속해서 들어가는 것이다. 여러분은 주어진 상태머신에 어떤 이벤트를 전달하기 전 딱 한번만 이 메소드를 호출해야 한다. QHsm::init()는 (1)초기의사상태에서 정의한 초기전이를 촉발하고, (2) 잎 상태에 도달한 때까지 상태계층 아래로 파고 들어간다.
init()를 비롯한 QHsm의 메소드 대부분은 헬퍼 매크로인 TRIGGER()를 내부적으로 사용한다.
4.4.2 이벤트 전달: dispatch() 메소드
어떤 사태가 이벤트를 처리하거나(이 경우 0을 반환), 최상위상태에 도달할 때까지(이 경우에도 0을 반환) 상태계층(책임사슬)을 검색하는 것이다.

4.4.3 정적/동적 상태전이: Q_TRAN() 매크로와 Q_TRAN_DYN() 매크로
Q_TRAN() 메소드는 상태 핸들러가 정적 상태전이를 수행하도록 한다. 그러나 이력으로 전이와 같은 경우에는 런타임타겟을 변경해야 할 때도 있다. 그리소 Q_TRAN()은 tranStat(), Q_TRAN_DYN()은 tran()을 호출하도록 하였다.
4.4.4 동적 상태전이: tran() 메소드
코딩 천재의 재수없는 코딩 실력
4.4.5 정적 상태전이: tranStat() 메소드와 Tran 클래스
매크로 TRAN()을 사용하는 이유를 설명했다.
4.4.6 QTran 객체 초기화: tranSetup() 메소드
tranStat()에 매개변수로 넘겨진 정적 Tran 객체는 주어진 전이가 처음 일어날 때 반드시 초기화돼야 한다. 이 초기화는 tranSetup()에서 이루어진다.
4.5 C로 상태머신 만들기
4.6 유의사항
선택점을 만나면 선택점을 switch에 넣고 case별로 코딩해 나가자.
동적전이가 힘들다. 그래서 5장에서 만나볼 것이다.
Q_INIT와 Q_TRAN이 동시에 존재하는 이유는 블라블라이다.
5. 상태패턴
OO 패턴과 마찬가지로, 상태패턴에는 5개의 필수요소가 있다.
- 패턴이름 – 문제, 해법, 패턴의 결과를 나타내는 1-2 단어.
- 문제 – 패턴이 해결하고자 하는 문제의 설명.
- 해법 – 해결책을 구성하는 요소와 그 역할
- 샘플코드 – 패턴의 예를 구체적으로 구현.
- 결과 – 패턴을 적용하고 난 결과와 장단점
5.1 Ultimate Hook
5.1.1 용도
공통 룩앤필을 제공한다. 그러나 클라이언트는 시스템 비헤비어의 모든 면을 특수화할 수 있다.
5.1.2 문제
시스템 수준의 소프트웨어에서 이러한 공통 룩앤필을 제공, 클라이언트 애플리케이션이 이를 기본값으로 쉽게 사용할 수 있게끔 하는 것.
5.1.3 해결책
차이점을 이용한 프로그래밍이다.
5.1.4 샘플코드
첫번째는 공용메소드로 라이브러리를 만들고 추가되는 메소드만을 패턴에 따라 만들도록 하는 것이다. 두번째는 추가되는 메소드를 C++ 가상함수로 선언한 다음 클라이언트가 UltimateHook 클래스를 파행해 이 상태 핸들러의 구현을 제공하도록 하는 방법이 있다.
5.1.5 결과
특수화 하위상태는 재정의할 이벤트만을 알고 있으면 된다.
특수화 하위상태에 영향을 주지 않고 최상위 공용 상위상태에 새 이벤트를 쉽게 추가할 수 있다.
클라이언트가 이미 사용하고 있는 이벤트의 의미를 제거하거나 변경하는 것은 어렵다.
특수화 하위상태에 많은 하위상태가 중첩돼 있을 경우, 많은 중첩상태를 거쳐 모든 이벤트를 전달하는 것에는 부담이 따른다.
5.2 Reminder
5.2.1 용도
이벤트를 만들어 자기자신에게 전달함으로써 상태차트 토폴로지를 보다 유연하게 한다.
5.2.2 문제
센서값을 읽고 프로세싱하는 상태머신을 시스템을 가정하자. 폴링과 프로세싱을 직교영역으로 두고 생각하면 이는 이벤트 전달에 있어 부담을 증가시키고, 영역 간의 복잡한 동기화가 필요하다. 게다가 폴링과 프로세싱은 정학히 직교하지 않는다.
5.2.3 해결책
느슨히 연결된 시스템 기능이 공통 이벤트로 강력하게 묶인 경우 사용된다. 즉, 하나의 이벤트가 2개의 행위를 발생시키는 트리거 역할을 할 때 이용된다.
이벤트(DATA_READY)를 만들어서 자기 자신에게 전달, 데이터 프로세싱 준비가 끝났음을 알리는 방법이 좋은 해결책이다. 이는 직교영역으로 구분하지 않고 복합상태를 만들어서 조건 검사후 하부상태로 전이토록 하는 방법이다.

5.2.4 샘플코드
5.2.5 결과
(1)두 수준의 중첩을 뚫고 들어가는 초기전이(비헤비어 상속 메타패턴에서는 혀용되지 않음)를 수행할 수 있다.

(2)긴 RTC 단계를 짧은 단계로 쪼개는 것이다. 긴 RTC 단계는 상태머신의 응답성을 나쁘게 하고 이벤트 큐에 더 많은 부담을 준다. Reminder 패턴은 계속할 작업에 관한 자극수단을 통해, CPU 집약적 처리(예: 반복처리)를 쪼개는데 도움을 준다.
5.3 Deferred Event
5.3.1 용도
이벤트의 시퀀스를 수정, 상태머신을 간단하게 한다.
5.3.2 문제
트랜젝션을 처리하는 서버 애플리케이션의 경우를 생각해보자. 트랜잭션이 시작하고 나면, 대개 원격 터미널로부터의 데이터 전송, 트랜젝션 승인으로 시작하는 처리 시퀀스를 따른다. 유감스럽게도 서버는 언제라도 새 트랜젝션 요청을 받을 수 있으므로, 이전 트랜잭션을 처리하느라 한참 분주할 때 요청을 받는 경우도 있을 수 있다. 요청을 무시하는 것이 한 가지 방법이 될 수 있겠지만 이것은 좋은 방법이 아니다. 다른 방법으로는 곧장 새 트랜잭션의 처리를 시작하는 것이다. 그러나 이렇게 하면 여러 개의 트랜젝션이 동시에 처리돼야 하기 때문에 작업이 엄청나게 복잡해진다.
5.3.3 해결책
해결책은 새 요청을 연기해놓고 펀리한 시간에 처리하는 것이다. 이렇게 하면 상태머신이 받은 이벤트 시퀀스를 수정하는 효과가 생긴다.

5.3.4 샘플코드
5.3.5 결과
이벤트 지연은 상태모델을 간단히 하는 중요한 기법이다.
5.4 Orthogonal Component
5.4.1 용도
상태머신을 컴포넌트로 활용한다.
5.4.2 문제
직교영역에서 베히베어 상속 메타패턴을 구현하기가 어렵다.
5.4.3 해결책
해결책은 직교영역 대신 객체조합을 사용하는 것이다.

부모는 자식에게 이벤트를 큐잉하지 않고 동기적으로 전달하지만, 자식은 부모에게 이벤트를 큐잉해 비동기적으로 전달해야 한다.
5.4.4 샘플코드
5.4.5 결과
- 독립된 비헤비어 각각을 반응형 객체로 분할한다. 이러면 객체는 독특한 비헤비어와 독특한 데이터를 모두 가지고 있게 된다.
- 분할에는 컨터에너-컴포넌트 관계(보모-자식, 마스터-슬레이브라고도 함)가 도입된다. 컨테이너는 주요 기능을 구현하고, 다른 이차적 기능은 컴포넌트에 위임한다. 컨테이너와 컴포넌트 모두 상태 머신이다.
- 컴포넌트는 다른 컨테이너에서도 재사용이 가능한 경우가 많다. 심지어는 같은 컨테이너 안에서도 재사용 될 수 있다. 컨테이너는 주어진 유형의 컴포넌트를 하나 이상 인스턴스화할 수 있기 때문이다.
- 컨테이너는 자신의 실행 스레드를 컴포넌트와 공유한다.
- 컨테이너는 이벤트를 직접 컴포넌테에 전달함으로써 컴포넌트와 정보교환을 한다. 컴포넌트도 이벤트를 전달함으로써 컨테이너에게 통지를 하지만, 직접적인 이벤트 전달은 아니다.
- 컴포넌트는 대개 Reminder 상태패턴을 이용, 컨테이너에 통지한다. 바꿔 말해 통지 이벤트는 내부 정보교환을 목적으로 만들어지므로 외부적으로 사용하기에는 적절치 않다. 주어진 유형의 컴포넌트가 많을 겨우 통지 이벤트는 자신을 만들어낸 컴포넌트를 반드시 명시해야 한다. 컴포넌트는 통지 이벤트의 매개변수에 자신의 ID를 명시해 넘겨준다.
- 커테이너와 컴포넌트는 데이터를 공유할 수 있다. 다양한 컨테이너의 인스턴스르르 여럿 허용하련느 목적에서, 대개 데이터는 컨테이너의 속성이다. 컨테이너는 선택된 컴포넌트에게 친구관계를 부여하는 것이 보통이다.
- 컨테이너는 자신의 컴포넌트를 전적으로 책임진다. 특히 컨테이너는 모든 컴포넌트에서 명시적으로 초기전이를 촉발해야 할 뿐 아니라, 컴포넌트에 이벤트를 명시적으로 전달해야 한다. 컨테이너가 자신의 상태에 있는 어떤 컴포넌트에 이벤트를 전달하지 않으면 오류가 발생한다.
- 컨테이너는 컴포넌트로의 이벤트 전달을 완벽하게 제어한다. 컨테이너는 컴포넌트에 적절치 않은 이벤트를 전달하지 않을 수 있다. 또 실행 도중 이벤트형을 변경할 수 있으며, 컴포넌트에 추가 정보를 제공할 수도 있다.
- 컨테이너는 컴포넌트를 동적으로 시작/중지할 수 있다(예를 들어 컴포넌트 상태머신의 어떤 상태에서).
- 상태먼신의 조합은 하나의 수준으로만 제한되지 않는다. 컴포넌트는 반응형 하위컴포넌트를 둘 수 있다. 다시 말해 컴포넌트는 하위수준 컴포넌트의 컨테이너가 될 수 있다는 것이다. 이러한 컴포넌트 중첩 수준에는 제한이 없다.
5.5 Transition to History
5.5.1 용도
복합상태를 빠져나온다. 그러나 최근에 활성화된 하위상태를 기억, 나중에 그 하위상태로 다시 돌아올 수 있게 한다.
5.5.2 문제점
UML 상태차트는 두 종류의 이력 의사상태로 해결한다. 바로 앝은 이력과 갚은 이력이다.
(복습)상태차트에 부가되는 특정한 기능을 표시하기 위하여 노드라는 것이 있다. 상태머신 그래프의 여러 노드를 아우르는 추상적 개념을 의사상태라고 한다.
5.5.3 해결책
최근에 활성화된 doorClosed 상태의 하위상태를 myDoorClosedHistory에 저장한다. 이 속성값을 설정하는 가장 좋은 곳은 doorClosed 상태의 탈출액션이다. 결과적으로 H*는 CLOSE 트리거의 타겟이 된다. 이력은 동적으로 변하기 때문이 Q_TRAN_DYN() 매크로를 이용하는 것은 당연하다.

5.5.4 샘플코드
QHsm::dispatch()의 구현에서는 전체 전이사슬을 완전히 실행한 뒤에야 현재 상태 포인터 QHsm::myState를 변경한다. 따라서 전이 도중이라도 QHsm::myState는 가장 최근의 활성상태를 가리키게 되며, 이것이 바로 우리가 원하던 바다.
5.5.5 결과
각 복합상태에 별도의 QState 멤버 포인터 함수를 제공, 상태의 이력을 저장해야 한다. 이 목적으로는 QHsm의 구상 하위클래스에 있는 속성을 사용하는 것이 가장 좋다.
QHsm::getState()를 이용, 복합상태의 탈출액션에서 이력 변수를 명시적으로 설정해야 한다.
주어진 상태의 이력으로 전이하려면, 최적화되지 않은 동적 Q_TRAN_DYN(<이력변수>) 전이 매크로가 필요하다.
얕은 이력이 아닌 깊은 이력의사 상태에에 해당한다.
이력 변수를 초기화하면 어떤 상태의 이력도 명시적으로 제거할 수 있다.
6. 상태모델 상속
우리는 상태핸들러를 사용해서 상태차트를 표현하기 때문에 상태를 상속하는 과정에서 상태 핸들러 간의 수많은 관계를 손사시키지 말아야 한다. 이 장에서 다루는 주된 내용이다.
좀 어려운것 같으면 나중에 읽으라고 한다.
PART II. 퀀텀 프레임워크
임베디드 리얼타임 시스템에서 상태머신을 실행하기 위해 만들어진 이벤트 구동형 아키텍처인 퀀텀 프레임워크를 설명한다. 요걸 잘 읽고 사용한다면 리얼타임 애플리케이션 개발용 코드생성툴을 하나 장만하는 것이다.
7. 퀀텀 프로그래밍이란
상태머신을 둘러싼 전체 기반구조를 잘 쌓아 놓았으니 유용하게 쓰시오.
액토(actor)라는 말 대신 능동객체(AO)라는 말을 선호한다. 능동객체의 가장 중요한 특성은, 자신의 내부구조를 외부 환경으로부터 철저히 차단하는 불투명한 캡슐 껍질이다. 객체의 내부, 외부를 막론하고 이 껍질을 뚫을 수 있는 유일한 개체는 이벤트 인스턴스이다. 이러한 통신 모델에서는 객체간의 모든 상호작용에서 따로 설계된 중간 객체(이벤트 인스턴스)를 필요로 한다.
7.1 멀티스레딩의 일반 접근법
능동객체의 정보교환에 왜 이토록 심한 제한을 두었을까? 능동객체에 기반한 컴퓨팅 모델은 모든 멀티스레딩 시스템에서 발생하기 마련인 병행성에 관련된 문제를 잘 해결할 수 있기 때문이다.
7.1.1 식사하는 철학자들 – 일반적 접근법
식사하는 철학자들 문제는 공유자원의 원활 한 분배를 의미한다. 이러한 분배는 세마포어와 뮤텍스라는 개념을 들고 왔음에도 데드락은 발생할 여지가 있다.
7.1.2 Therac-25 사건
레이스 컨디션이 발생해서 시스템에러를 일으켰다.
7.2 QF의 컴퓨팅 모델
객체에 적용된 메시지 전달(이벤트 교환) 패러다임은 능동객체의 개념을 낳았다. 병행 수행이 가능한 이벤트 구동형 객체는 경량형 제어 스레드를 부여받았다. QF 컴퓨팅 모델에서 애플리케이션은 기능산 특수화된 능동객체의 집합으로 구성된다. 각 능동객체는 상태차트를 포함하고 있므며, 서로 정보를 교환할 수 있는 유일한 수단은 이벤트 인스턴스를 교환하는 것 뿐이다.
7.2.1 능동 객체 기반 멀티스레딩
원자형 이벤트 처리 : 능동객체는 RTC를 이용, 한번에 하나의 이벤트를 처리한다. 능동객체는 별도의 제어 스레드에서 실행되므로 자유롭게 서로를 선점할 수 있으며, 따라서 좋은 반응성을 나타내고 CPU를 최적으로 사용할 수 있다.
비동기적 이벤트 교환 : 능동객체는 다른 능동객체와의 모든 이벤트 교환이 비동기적으로 발생한다고 가정할 수 있다.
확장성
관측성, 제어성, 시험성 : 능동객체는 그 자체가 유닛 테스트 단위로, 능동객체에 이벤트를 집어넣고 그 상태머신을 관찰하면 쉽게 테스트할 수 있다.
씬 와어어(thin wire) 형식의 정보교환 :
레거시 시스템의 캡슐화 :
7.2.2 양자이론과의 유사성
양자역학 모델은 상호작용이 최대 빛의 속도로 전파될 수 있다는 것을 명쾌히 설명해주지만, 기존 역학에 따르면 시간차 없이 상호작용이 일어난다는 부정확한 결론이 나온다. 소프트웨어 모델에 신호 전달 지연을 포함시키는 것은 점점 중ㅇ요해지고 있다. 클럭 속도가 엄청나게 빨라지고 있는 현재 전자 장비에서, 소프트웨어의 신호 지연을 감추는 것은 점점 더 어려워지고 있다. 양자이론과의 유사성을 통해, QP는 일반적인 병행성 문제 접근법에서처럼 이러한 지연을 감추지 말고 오히려 드러낼 것을 포그로그래머에 제안한다.
7.2.3 게시-구독형 이벤트 전달
이벤트 인스턴스를 생성자에서 사용자로 전달하는 매커니즘이 중요하다. 가장 간단한 매커니즘은 능동객체가 다른 능동객체로 이벤트를 직접 전달케 하는 것이다.
QF가 채택한 퀀텀 상호작용 모델은 2개의 요소로 이뤄진다. (1)모든 기본입자는 계속해서 특유의 가상양자를 ‘게시(publish)’하고, (2)모든 입자는 다른 입자가 발행한 가상양자의 부분집합만을 ‘구독(subscribe)’한다. 이것은 잘 알려진 게시 구독 모델이며 아래에 그 특징이 있다.
- 이벤트의 생산자와 소비자는 서로를 알 필요가 없다. (느슨한 연결)
- 이벤트의 종류는 명시적으로 알려져야 하고, 모든 경우에 그 의미가 같아야 한다.
- 중간자(mediator)는 게시된 이벤트를 받아들이고, 관심있는 구독자에게 이를 전달해야 한다.
- 다대다(many-to-many) 상호작용은 일대다(객체 대 중간자) 상호작용으로 바뀐다.
QF는 2개의 설계패턴을 결합한 것이다. QF 자신은 Mediator이고, QF가 이벤트를 중개하는 능동객체는 Observer이다.
7.2.4 일반 구조

QF에서 구축된 애플리케이션은 QActive와 QEvent를 상받아서 프레임워크를 확장한다. 애플리케이션은 QF API를 통해 통신, 시간 서비스를 사용한다. 그러나 대부분의 경우, 애플리케이션에서 RTOS API에 직접 접근하는 것은 좋지 않다.
7.2.5 돌아온 ‘식사하는 철학자들’
공유 리소스를 처리할 때 사용하는 일반적인 설계전략은, 공유 리소스를 별도의 능동객체에 캡슐화하고, 리소스의 관리를 능동객체에 맡기는 것이다. (다시 말해 리소스 공유는 직접 일어나지 않고 능동객체를 통해 일어난다.)
이 전략을 생각하는 철학자 문제에 적용해보면, 별도의 능동객체(이 예제에서는 Table이라 이름 붙임)로 포크를 관리한다는 결론이 나온다.

식사 그만이라는 TIMEOUT 시그널이 발생해서 DONE 시그널을 :Table에 전달해 주어야 :Table이 다른 필로소퍼에게 식사 권한을 줄 수 있다.
7.3 QF의 역할
보다 견고한 애플리케이셔늘 보다 빨리 개발하기 위함이다.
7.3.1 개념적 완전성
7.3.2 RTOS 추상 계층
애플리케이션이 다양한 작업을 처리하거나 많은 장치를 관리할 때는, RTOS를 통해 새트크를 분리해서 코드를 단순하게 할 수 있다.
7.3.3 소프트웨어 버스
소프트웨어 버스를 타자
7.3.4 고성능 병렬 컴퓨팅 플랫폼
7.3.5 자동코드생성의 기반
상태차트를 기반으로 코드를 만들어준다.
8장 퀀텀 프레임워크 설계
주제 : 프레임워크의 설계
소주제 :
QF의 플랫폼 독립적 컴포넌트의 구체적 코드를 소개한다.
QF를 하부 멀티태스킹 커널 없이 독립적으로 사용하는 방법과 다른 운영체제에 이식하는 방법에 대한 설명
8.1 임베디드 리얼타임 시스템
범용 컴튜터시스템과 임베디드 리얼타임 시스템이 가지고 있는 차이점에 대해서 설명하며 QF를 설계하는데 필요한 기술들에 대해서 알아본다. 다음 절 부터 우리는 QF 설계를 통해 임베디드 시스템 프로그램이의 특성을 활용하고 데스크탑에서 빌려온 기존 해법에 비해 구현을 단순화할 수 있는 몇 가지 분야를 알아본다. 이러한 분야에는 (1)오류와 예외조건 처리, (2)메모리 관리, (3)이벤트 전달, (4)초기화와 소거, (5)시간 관리 등이 있다.
8.2 오류와 예외조건 처리
데스크탑에서 예외조건으로 처리된 많은 상황들은 임베디드 시스템에서는 버그이다.
8.2.1 계약에 의한 설계
DBC(Design By Contract) 방법론을 사용한다.
DBC 방법론은 소프트웨어 시스템을 컴포턴트의 집합으로 보며 이 컴퓨넌트 집합은 상호책임, 다시 말해 계약을 상세히 정의한 명세에 기반을 둔다. 이 방식의 주된 개념은, 코드에 계약을 포함시키고 런타임에서 계약을 검증하는 것이다.
8.2.2 상태기반의 예외조건 처리
상태기반 예외처리는 대게 Ultimate Hook 상태패턴과 Reminder 상태패턴을 조합하는 것인데 이것이 안전하고 언어에 독립적인 방식이다. 이것은 프로그래밍 언어에 내장돼 있는 예외처리 매커니즘의 대안이 된다.
8.3 메모리 관리
QF와 호환성이 있고 임베디드 리얼타임 시스템에 적절한 메모리 관리 정책을 추천한다.
8.3.1 문제점 투성이 – 힙(Heap)
Heap 영역을 사용함에 따른 여러 문제점들이 있다.
8.3.2 QF의 메모리 관리
메모리 관리 측면에서 QF의 기본 설계 철학은, 간단한 변수를 제외하고 내부적으로 어떤 메모리도 사용하지 않는다는 것이다. 대신 QF는 프레임워크에서 파생된 모든 객체의 인스턴스화와, 운영에 필요한 메모리를 이용한 프레임워크 초기화를 사용자에게 위임해, 여러분의 입맛대로 메모리를 구성해 사용할 수 있는 완벽한 유연성를 제공한다.
8.4 상호배제와 블로킹
8.4.1 상호배제의 위험성
멀티스레딩을 수행하는 과정에는 교착상태(데드락)를 만들어 내는 원인으로써 상호배제기술이 있다. 동시에 공유자원에 접근해서 사용하는 것을 말한다. 그러나 이것은 데드락을 만들어낼 수 가 있는데 이것을 회피하기 위한 제일 간단한 기술은 인터럽트를 불가능하게 만드는 것이다.
8.4.2 능동객체 기반 시스템에서의 블로킹
- RTC 과정 사이에서 새 이벤트를 기다리기 위해
- RTC 과정 사이에서 다른 능동객체를 기다리기 위해
- 다른 능동객체와 관련 없는 사건을 기다리기 위해
블로킹은 마찰이다. 윤활유를 많이 넣어주란다.
8.5 이벤트 전달
이벤트 인스턴스는 정적일 수 없으며, 동적으로 생성되고 소멸되어야 한다.
능동객체는 이벤트를 소유하지 않는다. 능동객체는 하나의 RTC 단계(양자도약)가 수행되는 기간에만 유효한 이벤트를 빌려온다. 특히 능동객체는 전달 받은 이벤트를 가로채 다시 전달할 수 없다. 능동객체는 자신이 만든 이벤트를 반드시 내보내야 한다. 이들 이벤트가 궁극적으로는 자신에게 속한 것이 아니기 때문이다. 이벤트를 배달하고 자동으로 소멸하는 임무는 전적으로 QF가 맡는다. 따라서 자동 가비지 콜렉터에 해당하는 효과를 볼 수 있다.
그리고 이러한 QF 전달 매커니즘은 동적 이벤트 할당, 이벤트 구독, 이벤트 게시, 이벤트 멀티캐스팅(다중 구독의 경우), 자동 이벤트 재활용 등의 방법을 이용한다.
8.5.1 동적 이벤트 할당
힙을 사용할 때 고정블록크기 힙을 사용할 것이다. 단 대, 중, 소의 3가지 타입을 사용해서 힙사이즈를 줄일 것이다.
8.5.2 게시 구독 모델
QF의 설계에는 Observer 설계패턴과 Mediator 설계패턴이 조합을 이루고 있다. 여기서 능동객체는 감시자(Observer) 역할을 맡고, QF는 특수한 종류의 중재자(Mediator)인 변경관리자(Change manager)의 역할을 맡는다.
QF의 임무 3가지:
- 능동객체가 특정 신호를 구독하거나 구독하지 않을 수 있도록 인터페이스 QF::subscribe()와 QF::unsubscribe()를 제공한다.
- 게시 이벤트에 일반적으로 접근할 수 있는 인터페이스 QF::publish()를 제공한다.
- 이벤트 전달정책으로 정의하고, 전달주체의 전략을 업데이트한다.
8.5.3 이벤트 멀티 태스킹
QF::publish()와 QF::propagate()에서 구현된 순차적 멀티캐스트 매커니즘은 다음과 같은 조건에서만 ‘진짜’ 동시 멀티캐스팅과 흡사하다.
- 모든 구독자는 수정되지 않은 동일한 이벤트 인스턴스를 받아야 한다. 이러한 목적으로 상태 핸들러의 서명(4장)에서는 이벤트를 변경할 수 없게 QEvent const *e로 선언했다.
- 능동객체가 RTC 처리 도중 블록되지 않을 경우에만 처리 시퀀스가 구독자의 우선순위를 명확히 따른다.
- 우선 순위 기반의 선점형 스케줄러에서만 정확한 처리 시퀀스가 지켜진다.
8.5.4 자동 이벤트 재활용
QF의 이벤트 전달 매커니즘은 자동 이벤트 재활용을 염두에 두고 설계됐다. 모든 이벤트는 반드시 2개의 프레임워크 메소드를 통과해야 한다. 하나는 구독자 목록이 비어 있을 경우, 이벤트를 재활용하는 QF::publish()이고, 다른 하나는 목록의 마지막 구독자가 이벤트를 처리한 뒤 재활용하는 QF::propagate()이다.
이것은 QF::anihilate()를 통해서 이루어 지는데, 풀 이벤트는 항상 자신이 생겨난 풀에서 재활용된다. 그러나 풀 이벤트가 아닌 이벤트는 사용횟수를 지워서 재활용한다.
8.6 능동객체
클라이언트의 관점에서 QF 기반 애플리케이션 구축에 있어 가장 중요한 단계는 구체적인 능동객체 클래스를 생각해내는 것이다. QF는 구상(Concrete) 능동객체를 파생하는데 필요한 기반클래스를 제공한다. QF에서 이 기반클래스는 QActive이다. 이 클래스는 다음 세가지 필수요소를 가지고 있다.
- 이 클래스는 QHsm에서 에서 파생된 상태머신이다.
- 이벤트 큐를 가지고 있다.
- 실행 스레드를 가지고 있다.
8.6.1 내부 상태머신
QHsm 기반클래스를 간접적으로 상속해서, 비헤비어 상속 메타패턴의 강력함과 편리함으로 곧장 능동객체의 비헤비어를 구축할 수 있다. 왜냐하면 모든 구상 능동객체는 상태머신이기 때문이다.
8.6.2 이벤트 큐
이벤트 큐는 모든 능동객체 기반 프레임워크의 필수 컴포넌트이다. 이벤트 큐는 관련 능동객체가 언제나 이벤트를 수용할 수 있도록 한다. 또한, 이벤트 큐는 버퍼공간을 제공한다. 이 버퍼공간은 처리 가능 용량을 초과할만큼 많은 이벤트가 만들어지는 경우에 내부 상태차트를 보호한다.
8.6.3 실행 스레드
초기화 시퀀스의 어떤 위치에서 QF는 시스템의 모든 구상 능동객체 문맥에서 QActive::start()를 호출, 그 객체에 관련한 실행 스레드를 생성하고 실행해야 한다.
QActive::start()는 스레드를 설정한다고 보면 되며, 설정된 스레드는 QAcitve::run()이 처리한다.
8.7 초기화와 삭제
협력관계에 있는 능동객체 집합과 같은 멀티스레드 애플리케이션의 초기화와 삭제에는 두가지 면이 있다. 하나는 이미 설명한 바와 같이 메모리의 할당/재활용과 관련된 것이며, 다른 하나는 이 절에서 다룰 내용인데, 멀티태스킹을 시작하고 종료하는 것에 관한 것이다.
8.7.1 프레임워크 초기화
능동객체 기반 멀티스레딩을 시작하려면, 먼저 프레임워크 자체의 기본적인 초기화를 수행해야 한다. 여기서는 2개의 메소드를 호출한다. QF::Init()를 한번 호출하고, 초기화하려고 하는 각 이벤트 풀에서 QF::PoolInit()를 호출한다.
8.7.2 QF 애플리케이션 시작
QActive::start() 메소드는 어떤 순서로도 능동객체를 시작할 수 있는 유연성을 부여한다.
8.7.3 QF 애플리케이션의 올바른 종료
임베디드 프로그램은 고상하게 종료할 필요가 없다.
8.8 시간 관리
시존 리얼타임 커널에서 사용 가능한 시간관리로는, 다양한 객체의 시간제한 블로킹(세마포어, 메시지 큐), 스레드 호출의 지연(sleep()), 다양한 타이머를 이용한 사용자 제공 콜백함수의 지연 호출(시그널)을 들 수 있다. 그러나 이들 매커니즘은 능동객체 기반 시스템에서 그다지 유용하지 않다.
시간관리를 능동객체 기반 멀티스레딩에서 사용하려면, 모든 관심있는 사건은 이벤트 인스턴스를 통해 자기자신을 명시한다는 일반적인 패러다임을 사용해야 한다.
8.8.1 QTimer 클래스
QF는 QTimer 클래스에서 인스턴스화된 타이머를 통해 시간을 관리한다. 이 타이머의 기본적인 사용모델은 다음과 같다. 능동객체는 하나 이상의 QTimer 객체를 할당, 저장위치를 제공한다. 능동객체가 타임아웃을 준비해야 할 때에는 , 자신의 타이머 가운데 하나를 활성화해서 타임아웃 이벤트를 촉발한다.
타이머를 활성화하려면, 능동객체는 그 포인터와, 전달할 타임아웃 신호와, 전달 전에 경과해야 할 클럭 틱의 수를 반드시 제공해야 한다.
8.8.2 클럭 틱 – QF::tick() 메소드
활성화된 타이머를 서비스라혀면, 가급적 클럭 틱 인터럽트로부터 QF::tick()을 주기적으로 호출해야 한다.
8.9 QF API의 간단한 레퍼런스
QF의 클라이언트는 3개의 프레임워크 클래스에만 신경쓰면 된다. QF 클래스와, 구상 능동객체를 파생하는데 사용하는 기반클래스인 QActive 클래스, 그리고 타이머를 인스턴스화하는 용도의 QTimer 클래스이다.
8.9.1 QF 인터페이스
QF 클래스는 Singleton 설계패턴을 사용하지 않는다. 하위클래스 파생을 통한 확장성이 필요없고, instance() 메소드를 토해 Singleton에 접근하는 추가적 부담이 용납되지 않기 때문이다.
- getVersion()
- init()
- poolInit()
- tick()
- create()
- subscribe()
- unsubscribe()
- publish()
- background()
- cleanup()
8.9.2 QActive 인터페이스
- QAcitve()
- start()
- postFIFO()
- postLIFO()
- stop()
8.9.3 QTimer 인터페이스
- fireIn()
- fireEvery()
- disarm()
- rearm()
9장 퀀텀 프레임워크 구현
첫번째 목표는 프레임워크에 플랫폼 종속적인 부분을 채워넣어 애플리케이션에서 사용할 수 있는완전한 코드를 만든다. 두번째 목표는 클라이언트가 선택한 환경에 QF를 이식하는 방법을 보이기 위해 다양한 플랫폼을 선택해 QF를 다양한 범위의 애플리케이션에 적응시키는 방법을 설명한다.
9.1 QF – Parnas 패밀리
저자가 Parnas씨를 존경하나 보다. 8장에서는 QF 설계의 공통성을 뽑아내 QF 설계 트리의 뿌리를 구축했다. 이제 9장에서는 나뭇가지와 잎에 초점을 맞추려한다. 이들 요소는 구체적인 QF 포트로 구성되는데, 이들 포트가 모여 QF의 조그마한 Parnas 패밀리가 만들어진다.
9.2 코드 구성
QActive 클래스는 플랫폼 의존적이다. 따라서 QActive 클래스 내에서 정확히 설정돼야 하며, 미리 할당되거나 프레임워크 안으로 숨어버려서는 안된다.
9.2.1 디렉터리 구조
인터페이스를 패키지 범위(넓은)와 공용 범위(좁은)로 나눈 것은, 패키지 내의 강한 결속력과 외부와의 느슨한 연결을 반영한다.
공용범위의 헤더파일(Cpp/Qf/Include/qf_win32.h)은 플랫폼 의존적 인터페이스를 QF라이브러리에 제공한다.
패키지 범위 수준 인터페이스(Cpp/Qf/Win32/port.h)는 공용 헤더파일을 포함하며, QF 포트를 구축하는데 필요한 추가 인퍼페이스(QF 모듈 사이의 의존성)를 지정한다. 플랫폼 독립적 구현파일과 의존적 구현파일은 다른 디렉터리에 위치하더라도 모두 같은 QF 패키지에 속한다. 그리고 이들 모듈은 모두 패키지 범위의 헤더파일 port.h를 포함한다.
모든 플랫폼 독립적 QF 소스파일은 Cpp/Qf/Source 디렉터리에 있으며, 구체적인 디렉터리를 지정하지 않고 헤더파일 port.h를 포함시켰다(#include “port.h”). port.h의 구체적인 버전은QF 라이브러리를 지정된 디렉터리에 구축하면 자동으로 선택된다(예: Win32 포트의 경우 Cpp/Qf/Win32).
9.2.2 공용범위 헤더파일
Win32 QF 포트에서 쓰이는 공용범위 헤더파일(플랫폼 의존적 QF 인터페이스)의 디렉토리 구조를 확인한다. 그리고 RTKernel-32 포트에서는 이벤트 큐(qequeu.h)와 이벤트 풀(qepool.h)이 필요없다. 이들 요소는 하부 RTOS인 RTKernel-32의 구현에 기반을 두고 있기 때문이다.
9.2.3 패키지범위 헤더파일
패키지범위 헤더파일이라 함은 넓은 인터페이스 헤더파일을 의미하며 공용 인터페이스(qf_win32.h)와 플랫폼 독립적 패키지범위 헤더파일(qfpkg.h)로 구성되는 Cpp/Win32/port.h 파일로 구성된다. 그리고 이 파일에는 Cpp/Source 파일을 이용하여 플랫폼 의존적 작업을 코딩할 때 쓰이는 몇 가지 매크로를 정의한다.
9.3 공통요소
QF의 공통 플랫폼 의존적 요소인 임계영역, 이벤트 풀, 이벤트 큐에 대해서 다룬다.
9.3.1 임계영역
QF는 어떤 작업을 방해 받지 않고 수행해야 한다. 방해 받지 않고 수행되어야 하는 코드영역을 보호하는 가장 간단하고 효율적인 방법은, 영역에 진입할 때 인터럽트를 불가능하게 하고, 빠져나올 때 영역을 다시 가능하게 하는 것이다.
9.3.2 이벤트 풀
이벤트 풀은 이벤트 인스턴스를 저장하려는 용도로 특수하게 만들어진 고정된 블록크기의 힙이다. 여기서는 이벤트 풀클래스의 선언과 구현방법에 대해서 알아본다.
9.3.3 이벤트 큐
이 절에서는 능동객체를 위해 설계되고 최적화된 이벤트 큐의 구현을 설명한다.
QEQueue 클래스는 4개의 메소드가 있다.
init() 이벤트 큐를 초기화한다.
get() 큐에서 이벤트를 추출한다.
putFIFO() FIFO 처리를 위해 큐에 이벤트를 삽입한다.
putLIFO() LIFO 처리를 위해 큐에 긴급한 이벤트를 삽입한다.
9.4 DOS : 멀티태스킹 커널이 없는 QF
Microsoft DOS와 같이 하부 멀티태스킹 커널이 없는 단일스레드 한경에서 QF를 적용시키고 사용하는 방법을 설명한다.
9.4.1 포그라운드/백그라운드 처리
비동기적 이벤트를 즉시 처리하는 ISR로 구성되는 포그라운드 처리.
남아있는 CPU 사이클을 모두사용하면서 시간에 그다지 쫓기지 않는 액션을 수행하는 무한루프인 백그라운드 처리.
포그라운드/백그라운드 시스템의 실행 프로파일을 보면 인터럽트가 발생하는 타이밍에 큰 영향을 받는다는 것을 볼 수 있다.
9.4.2 QF와 프그라운드/백그라운드
QF를 이용하면, QF::background()를 이용해서 능동객체 모드를 백그라운드 처리할 수 있다.
9.4.3 DOS 전용 코드
QF::poolInit(…);
activeA.start(…);
…
for(;;) {
QF::background();
..
}
9.4.4 포그라운드/백그라운드 시스템에서 QF의 장점
가장 중요한 이점이라면, 신뢰성있고 안전한 방법으로 인터럽트 수준(포그라운드)과 작업수준(백그라운드)이 정보를 교환할 수 있다는 것이다. 이는 ISR이 이벤트를 게시할 수 있기 때문이다.
9.5 Win32 : 테스크탑에서의 QF
PC는 타겟 하드웨어를 사용할 수 있기 전에도, 친국하고 강력하며 가격까지 싼 툴을 이용해 작업할 수 있게 해조고, 코드의 가시성을 훌륭하게 만들어준다.
9.5.1 임계영역, 이벤트 큐, 실행 스레드
9.5.2 클럭 틱
9.6 RTKernel-32 : QF와 선점형 우선순위 기반 커널
이 절에서는 QF를 설계의 주요 목표 환경인, 우선순위 기반 선점형 리얼타임 멀티태스킹 커널에 대한 QF 포트를 설명한다.
9.6.1 임계영역과 이벤트 풀
9.6.2 이벤트 큐와 실행 스레드
9.6.3 RTKernel-32 초기화와 클럭 틱
9.6.4 RTKernel-32를 이용한 크로스 개발
10장 샘플 퀀텀 프레임워크 애플리케이션
본장의 앞 부분은 QF 애플리케이션의 제작 방법에 대해서 설명한다. DPP를 예로 들 것이다. 뒷 부분은 일반적인 능동객체 기반 프레임워크와 QF에 관련한 규칙, 휴리스틱(heuristic), 주의사항, 비용 등에 초점을 맞춘다.
10.1 QF 애플리케이션의 제작
QF를 이용해서 (1)신호와 이벤트를 선언하고, (2) 능동객체를 정의한 다음, (3) 프레임워크를 초기화하고 능동객체를 시작해서 구현한다.
10.1.1 신호와 이벤트

위와 같은 시퀀스 다이어그램을 그리면서 QF 애플리케이션의 구축을 시작한다. 이렇케 하면 필요한 신호를 찾을 수 있고, 특정 이벤트 매개변수에 관해 생각해볼 수 있다.
4.1.1에 요약을 다시 읽어 보자.
10.1.2 능동객체 Table
별도의 능동객체에 공유 리소스를 캡슐화하는 것이 공유 리소스를 처리하는 일반적인 규칙이다. DPP 예제의 경우 공유 리소스는 포크이다. Table 객체는 포크를 캡슐화하고, 시스템의 나머지 부분을 위해 이 객체를 관리한다.
myFork[] 속성과 isHungry[] 속성은 이 상태의 정량적인 면(확장상태변수)을 나타낸다. Table 능동객체는 EAT_SIG를 발생시키며, HUNGRY_SIG과 DONE_SIG 시그널을 받아들인다.
그래서 QF::subscribe(this, HUNGRY_SIG), QF::subscribe(this, DONE_SIG)를 실행해서 이 2개의 시그널을 감시한다.
그런 후, switch(e->sig)을 이용, HUNGRY_SIG가 들어오면 포크가 여유있는지 확인 한 후, 만족하면 EAT(n)신호를 publish(pe)(pe는 publish event의 약자)를 통해서 발생시키고, 만족하지 않으면 isHUNGRY[n]을 ‘1’로 설정 배고픈 상태임을 보여준다.
DONE_SIG가 들어오면 배고픔 상태이면서 포크 상태를 체크(좌/우)하고 포크 상태가 ‘가능’이면 EAT(n)신호를 publish한다.
10.1.3 Philosopher 능동객체
Philosopher 능동객체는 myNum 속성을 선언, 서로 다른 객체를 구분한다. 그리고 모든 철학자의 thinking 상태나 eating 상태에 머무른 시간을 추적해야 한다. 이를 위해 각 철학자용 전용 QF 타이머인 myTimer를 사용한다.
10.1.4 DPP 배치
먼저 능동객체 고유의 우선순위를 선택해야 한다. 예를 들면, Philosopher 능동객체에는 번호(1~N)에 맞춰 우선순위를 할당하고, Table 능동객체에 가장 높은 우선순위(N+1)를 할달할 수 있다.
다음으로는 이벤트 큐, 이벤트 풀, 스택과 같은 다양한 메모리 버퍼의 크기를 결정하는 것이다.
10.1.5 참고
내가 얼마나 머리가 좋은지 알아?
10.2 QF 애플리케이션 개발 규칙
- 능동객체는 리소스를 공유할 수 없다.
- 능동객체는 비동기적 이벤트 교환을 통해서만 상호작용할 수 있다.
- 능동객체는 RTC 처리 도중 블록되어 다른 능동객체를 기다릴 수 없다. 특히 이벤트 생성 중(빈 메모리 풀 때문)이나 이벤트 게시 중(꽉 찬 이벤트 큐 때문)에 블록할 수 없다.
10.3 애플리케이션 개발에 있어서의 휴리스틱
능동객체 기반 프로그래밍은 멀티스레딩에 대한 기존 접근의 패러다임 전환을 요구한다. 기존 접근법에서는 공유 리소스와 다양한 동기화 매커니즘에 초점을 맞춘 반면, 능동객체 기반 접근법에서는 문제의 분할을 고민하고 능동객체 사이에서 교환되는 이벤트를 중시한다.
우리의 주된 목표는 능동객체간 커플링을 최대한 느슨하게 하는 것이다. 리소스 공유를 피하고, 교환되는 이벤트의 수와 크기의 관점에서 최소한의 정보교환만이 필요하도록 문제를 분할해야 한다.
리소스 공유를 피하는 주된 전략은, 시스템 나머지 부분의 리소스를 관리하는 별도의 능동객체에 리소스를 캡슐화하는 것이다.
능동객체의 응답성은 상태머신의 가장 긴 RTC 단계로 결정된다. 하드 리얼타임 기한을 충족하려면 긴 프로세스를 짧은 단계로 쪼개거나, 우선순위가 낮은 다른 능동객체로 옮겨야 한다.
능동객체 기반 애플리케이션의 개발에 있어 좋은 출발점은, 주요 쓰임새에 관한 시퀀스 다이어그램을 그리는 것이다. 이 다이어그램은 신호와 이벤 매개변수를 파악하고 능동객체의 구조를 결정하는데 많은 도움이 된다.
첫번째 시퀀스 다이어그램을 작성하고 나면, 곧장 실행모델을 만들어야 한다. 특히 QF는 많은 부분 완성되지 않은(사실상 빈) 프로토타입을 구축하고 실행하는 것도 가능하도록 설계됐다. QF의 높은 이식성은 여러분의 최종 목표와는 다른 플랫폼에서 모델을 구축할 수 있게 해준다.
대부분의 경우 능동객체의 내부 상태머신에 대부분의 시간을 할애하고, 다른 면(실행 스레드나 이벤트 큐 등)은 무시할 수 있다. 사실 QF 애플리케이션의 제작 과정은 능동객체의 상태머신을 정교하게 만드는 것이 대부분이다. 강력한 비헤비어 상속 메타패턴(4장)과 기본 상태패턴(5장)을 이용하면 이 부분을 쉽게 해결할 수 있다.
10.4 이벤트 큐와 이벤트 풀의 크기
개발 초기에는 크게 가고 개발 중간 또는 막바지에 그 크기를 줄여나간다.
10.4.1 이벤트 큐
DPP 애플리케이션에서 모든 Philosopher 능동객체는 매우 적은 처리만을 수행한다(짧은 RTC 단계를 가지고 있다). CPU가 하나의 클럭 틱 안에 이 RTC 단계를 완수한다면, Philosopher 큐의 최대 길이는 이벤트 3개가 될 것이다. 하나는 클럭 틱 ISR에서 온 것이고, 둘은 Table 능동객체에서 온 것이다.
- 이벤트 큐의 크기는 능동객체의 우선순위에 좌우된다. 일반적으로 우선순위가 높을수록 이벤트 큐의 길이는 짧다. 특히 시스템에 있는 가장 높은 우선순위의 능동객체는 다른 능동객체에 의해 게시된 모든 이벤트를 즉시 소모하므로, ISR이 게시한 이벤트만 큐잉하면 된다.
- 큐의 크기는 높은 우선순위 능동객체와 ISR의 모든 선점을 포함한, 가장 긴 RTC 단계의 지속시간에 좌우된다. 처리가 빠를수록, 필요한 이벤트 큐의 길이도 짧아진다. 큐의 크기를 최소화하려면 아주 긴 RTC 단계를 피해야 한다. 이상적으로는, 주어진 능동객체의 모든 RTC 단계를 마치는데 있어 대략 같은 수의 CPU 사이클이 요구돼야 한다.
- 서로 관련된 이벤트의 생성은 큐의 크기에 부정적인 영향을 미칠 수 있다. 예를 들어, ISR이나 능동객체가 하나의 RTC 단계에서 여러 이벤트 인스턴스를 만드는 경우가 종종 있다. 예를 들어 Table 능동객체는 이따금 2개의 식사권한을 생성한다. 여러분이 큐 크기의 최소화에 가장 비중을 두고 있다면, 이벤트 생성을 여러 RTC 단계에 퍼뜨리는 식으로 이렇게 이벤트가 많이 발생하는 것을 막아야 한다.
일반적으로 능동객체는 우선순위 순서에 따라 낮은 우선순위의 능동객체부터 시작하는 것이 가장 안전하다. 우선순위가 낮은 능동객체는 큰 이벤트 큐를 갖기 때문이다.
10.4.2 이벤트 풀
대게의 이벤트 게시 시나리오에서는 먼저 이벤트를 생성하고(포인터를 저장할 임시변수를 할당), 이벤트 매개변수를 채워 넣은 담음 이벤트를 게시한다.
10.5 시스템 통합
어떠한 장치도 공유 리소스로 간주해야 한다. 따라서 장치로의 접근권한도 하나의 능동객체에 제한해야 한다. 단, 하나의 능동객체에서만 장치에 접근할 수 있다는 것이 반드시 모든 장치에 별도의 능동객체를 만들어야 한다는 뜻은 아니다. 하나의 능동객체를 이용해 많은 장치를 캡슐화할 수도 있는 경우도 많다.
10.6 요약
11장 결론
QP의 핵심요소, 다른 프로그래밍 경향과의 관계, 필자가 예상한 QP의 파급효과를 간단히 요약하낟.
11.1 QP의 핵심요소
QP는 두가지 기본 개념에 기반을 둔 프로그래밍 패러다임이다. 바로 (1)계층형 상태머신과 (2)능동객체 기반 컴퓨팅 모델이다.
11.1.1 도구가 아닌 설계 유형
계층형 상태머신은 다른 소프트웨어 개념처럼 가력한 설계 유형이지 특정한 툴이 아니다.
11.1.2 모델링 도우미
실행가능한 프로토타입을 지원하므로 QF는 중량형이고 형식적인 CASE 툴의 경량형 대안을 제공한다. CASE 툴의 가장 큰 판매전략은 항상 신속한 프로토타입 제작이었다. 사실 QP는 설계 자동화 툴의 좋은 특징을 많이 모방하고 있다. QP가 CASE 툴과의 큰 차이점은 CASE 툴이 대개 UML과 같은 비주얼 모델링 언어를 사용하는 반면 QP는 직접 C++나 C를 사용한다는 것 뿐이다.
11.1.3 학습 도우미
필자의 OOP 이해도는 C에서 바닥부터 객체지향을 구현하고 난 뒤 많이 높아졌다고 생각한다. 필자는 지극히 객체지향적인 방식으로 C++를 꽤 오랫동안 사용했다. 그러나 OOP가 내부적으로 어떻게 동작하는 알고 난 뒤에야, 필장의 진정한 도구가 됐다. 필자는 이때부터 OOP를 특정한 프로그래밍 언어의 활용법이 아니라 설계방식으로 생각하게 됐다. 이러한 사고방식을 통해 더 많은 시스템에서 기초적인 OOP 개념을 하나의 피턴으로 인식할 수 있었으며, 이어서 기존의 많은 구현들을 이해하고 개선하는데 많은 도움이 됐다.
필자는 이 경험을 한번 더 반복했다. 이번에는 계층형 상태머신과 능동객체 기반 컴퓨팅 모델의 개념이었다. 필자는 ROOM을 공부하고 있었고, 다양한 툴을 이용해 상태모델을 구축했지만, 비헤비어 상속과 능동객체 기반 프레임워크를 구현하고 나서야 개념을 확실히 잡을 수 있었다.
11.1.4 유용한 은유
- 소프트웨어의 개념적 완전성을 촉진할 수 있다.
- 공통된 용어를 제공, 커뮤티케이션을 향상시킬 수 있다.
- 최종 제품을 더 쉽게 사용할 수 있게 한다.
- 새로운 개념을 더 빨리 학습할 수 있다.
11.2 QP의 목적
QP, 양자물리학, 그리고 OOP는 대칭성을 가지고 있다.
11.2.1 퀀텀 프로그래밍 언어
QP를 프로그래밍 언어에 통합하면 많은 이점이 생길 수 있다. 첫째, 컴파일러는 상태머신의 일관성과 적법성을 검사할 수 있으므로, 컴파일 타임에서 많은 오류를 제거할 수 있다. 둘째, 컴파일러는 상태머신 인터페이스를 단순화해서 클라이언트에 제공할 수 있다. 예를 들어 현재 QP 구현에 있는 몇몇 인위적인 제한을 없앨 수 있다. 셋째, 컴파일러는 코드의 최적화를 보다 잘 할 수 있다.
11.2.2 미래의 RTOS
상태머신과 능동객체 기반 컴퓨팅은 진정한 범용 개념이고, RTOS와 견고하게 통합돼야 한다. 이러한 통합에 따른 장점은 최소 세 가지다. 첫째, 능동객체는 상호배제와 블로킹에 기반을 둔 기존의 스레딩보다 더 안전하고 나은 컴퓨팅 모델을 제공한다. 둘째, QF 구현에 필요한 개념의 제공을 통해, RTOS에서 기존에 지원했던 많은 매커니즘의 필요성을 없앴다. 따라서 통합 시스템은 RTOS 자체보다 더 크지 않을 것이다. 세번째, 이러한 통합 RTOS는 개방형 아키텍처 구축에 필요한 표준 소프트웨어 버슨를 제공한다.
11.2.3 하드웨어/소프트웨어 통합설계
11.3 제안
- Linux, VxWorks, QNX, eCos, MicroC/OS 등 새로운 운영체계와 플랫폼에 QF를 이식한다.
- QF와 스케줄러를 견고하게 통합, 기존 RTOS의 대체품을 제공한다.
- 비헤비어 상속 메타패턴을 이용, 새로운 상태패턴을 정확히 포착해 문서화하낟.
- C/C++ 이외의 언어로 QP를 구현한다. 일례로 Java를 들 수 있다.
- 오픈소스 C/ㅊ++ 컴파일러를 수정해서 퀀텀 프로그래밍 언어를 구현할 수 있는 가능성을 타진한다.
- 재사용 가능한 능동객체 컴포넌트를 만든다.
