DDS WaitSet 메커니즘과 동시성 모델
2025-12-13, G30DR
1. 서론: 실시간 분산 시스템에서의 이벤트 처리 패러다임 변화
분산 시스템 아키텍처, 특히 데이터 분산 서비스(DDS: Data Distribution Service)와 같은 실시간 데이터 중심(Data-Centric) 미들웨어 환경에서 데이터의 도착과 상태 변화를 감지하고 처리하는 방식은 시스템 전체의 성능과 안정성을 결정짓는 가장 중요한 설계 요소 중 하나다. 현대의 복잡한 국방, 항공, 산업 자동화 시스템은 수천 개의 데이터 포인트가 밀리초(ms) 단위로 갱신되는 환경을 전제로 하며, 이러한 고속 데이터 흐름 속에서 CPU 자원을 효율적으로 관리하면서도 데이터 처리의 즉시성(Timeliness)을 보장해야 하는 난제에 직면해 있다.
DDS 표준은 이러한 요구사항을 충족하기 위해 크게 두 가지의 상반된, 그러나 상호 보완적인 통신 처리 모델을 제공한다. 첫 번째는 미들웨어가 데이터 도착 즉시 사용자 코드를 호출하는 리스너(Listener) 기반의 비동기 통지(Asynchronous Notification) 모델이며, 두 번째는 애플리케이션이 자신의 주도하에 데이터 도착을 기다리는 웨이트셋(WaitSet) 기반의 동기적 대기(Synchronous Wait) 모델이다.1
초기 분산 시스템 설계에서는 구현의 단순함과 즉각적인 반응성 때문에 리스너 패턴이 널리 사용되었으나, 멀티코어 프로세서의 보편화와 시스템 로직의 복잡도 증가로 인해 리스너 모델의 한계가 명확해지고 있다. 리스너 모델은 미들웨어의 내부 쓰레드를 ‘빌려 쓰는’ 구조적 특성상, 사용자 로직이 길어지거나 블로킹(Blocking) 연산을 수행할 경우 미들웨어 전체의 통신 안정성을 해칠 위험이 크다.2 이에 반해 웨이트셋은 개발자가 명시적으로 생성한 ’애플리케이션 쓰레드’를 활용하여 데이터를 처리함으로써, 실행 컨텍스트의 제어권을 애플리케이션이 온전히 소유하게 한다. 이는 ’바쁜 대기(Busy-waiting)’로 인한 CPU 낭비를 방지하면서도, 복잡하고 시간이 소요되는 로직을 미들웨어의 간섭 없이 안전하게 수행할 수 있는 기반을 제공한다.
본 보고서는 DDS WaitSet의 아키텍처적 원리와 내부 구현 메커니즘을 유닉스(Unix)의 select() 모델과 비교 분석하고, 멀티코어 환경에서의 병렬 처리 효율성, 그리고 교착 상태(Deadlock) 회피를 위한 안전성 측면에서 WaitSet이 갖는 기술적 우위를 심층적으로 규명한다.
2. WaitSet의 아키텍처와 작동 원리: 제어의 역전과 회복
2.1 WaitSet의 정의 및 역할: 어플리케이션 주도형 처리
WaitSet은 하나 이상의 조건(Condition) 객체들을 논리적으로 그룹화하여 관리하며, 이들 중 하나라도 ‘트리거(Trigger)’ 상태가 되었을 때 블로킹되어 있던 애플리케이션 쓰레드를 깨우는 동기화 객체다.4 이는 리스너가 미들웨어에 의해 수동적으로 호출되는 ‘푸시(Push)’ 방식인 것과 대조적으로, 애플리케이션이 능동적으로 데이터를 가져오는 ‘풀(Pull)’ 방식을 취한다.
웨이트셋의 핵심 설계 철학은 **“제어권의 회복(Recovery of Control)”**이다. 리스너 방식에서는 데이터가 도착하는 즉시 미들웨어 쓰레드가 애플리케이션 코드로 진입(Entry)하므로, 애플리케이션은 자신의 실행 시점을 통제할 수 없다. 데이터가 폭주할 경우 리스너는 끊임없이 호출되며, 이는 애플리케이션이 처리 용량을 초과하는 부하를 감당하게 만든다. 반면 WaitSet을 사용하면 애플리케이션은 자신의 처리 능력에 맞춰 wait()를 호출하고, 한 번에 여러 데이터를 배치(Batch)로 가져와 처리하는 등 흐름 제어(Flow Control)를 주도적으로 수행할 수 있다.2
2.2 Unix select() 모델과의 기술적 유비(Analogy) 및 심층 비교
사용자의 쿼리에서 언급된 바와 같이, WaitSet의 동작 원리는 유닉스 시스템 프로그래밍의 select() 함수와 개념적으로 매우 유사하다. 이는 여러 개의 I/O 소스를 하나의 쓰레드에서 효율적으로 감시하는 I/O 멀티플렉싱(Multiplexing) 기법의 일종이다.6 그러나 단순한 유사성을 넘어 내부 구현과 동작 방식에는 중요한 차이가 존재한다.
표 1. Unix select()와 DDS WaitSet의 비교 분석
| 비교 항목 | Unix select() | DDS WaitSet |
|---|---|---|
| 감시 대상 | 파일 디스크립터 (File Descriptor, FD) | 조건 객체 (Condition Objects) |
| 동작 레벨 | 커널 레벨 (Kernel Space) | 사용자 공간 (User Space) 및 미들웨어 |
| 블로킹 메커니즘 | 커널의 Wait Queue 및 인터럽트 기반 | 세마포어(Semaphore) 또는 조건 변수 8 |
| 확장성 (Scalability) | O(n) 복잡도, FD 개수 제한(FD_SETSIZE) 존재 9 | O(1) 또는 O(n) (구현체별 상이), OS 제한과 무관 |
| 컨텍스트 | OS 시스템 콜 (System Call) | 미들웨어 API 호출 |
| 상태 유지 | 호출 시마다 FD 셋을 재설정해야 함 (Stateless) 10 | 조건 객체를 한 번 Attach하면 유지됨 (Stateful) 5 |
2.2.1 개념적 동형성
select()가 읽기, 쓰기, 예외 상황에 대한 파일 디스크립터의 집합(Set)을 감시하듯, WaitSet은 ReadCondition, StatusCondition, GuardCondition의 집합을 감시한다. 두 메커니즘 모두 ’관심 있는 이벤트’가 발생할 때까지 호출한 쓰레드를 대기 상태(Blocked State)로 전환하여 CPU 자원을 보존한다는 점에서 동일한 목적을 공유한다.
2.2.2 내부 구현의 차이와 최적화
RTI Connext Micro와 같은 구현체의 내부를 살펴보면, WaitSet은 내부적으로 **세마포어(Semaphore)**를 사용하여 구현된다.8 select()가 커널의 인터럽트 핸들러와 큐를 직접 사용하는 시스템 콜인 반면, WaitSet은 미들웨어 레벨에서 추상화된 동기화 객체다. WaitSet은 내부적으로 조건 변수와 타머를 결합한 세마포어를 통해 구현되며, 데이터가 도착하면 미들웨어의 수신 쓰레드(Receive Thread)가 해당 세마포어를 ‘Give’ (Unlock) 하여 애플리케이션 쓰레드를 깨운다.8 이는 select()가 매번 호출될 때마다 커널 영역으로 데이터를 복사하고 전체 FD 리스트를 스캔해야 하는 오버헤드(O(n))를 피할 수 있는 구조적 이점을 제공한다. 일부 고성능 DDS 구현체는 이벤트 기반의 비동기 통지 메커니즘을 활용하여 epoll과 유사한 O(1) 성능을 내도록 최적화되어 있다.10
2.3 Condition 객체: 이벤트의 추상화 및 트리거 메커니즘
WaitSet은 단독으로 작동하지 않으며, 반드시 Condition 객체와 결합하여 사용된다. Condition은 DDS 엔티티의 상태 변화를 추상화한 객체로, WaitSet이 무엇을 기다릴지를 정의한다.
표 2. DDS Condition 객체 유형 및 상세 기능
| 조건 유형 (Condition Type) | 트리거 원천 (Trigger Source) | 주요 활용 사례 | 상세 동작 메커니즘 |
|---|---|---|---|
| StatusCondition | Entity의 통신 상태 변화 (Communication Status) | 데이터 도착(DataAvailable), QoS 위반(DeadlineMissed), 연결 상태 변화(LivelinessChanged) | 엔티티(DataReader 등)에 내장되어 있으며, enabled_statuses 마스크를 통해 감시할 상태를 필터링함.13 상태 플래그가 set 되면 트리거됨. |
| ReadCondition | 데이터 리더 큐(Queue)의 데이터 존재 여부 | 특정 상태(예: 읽지 않음 NOT_READ)나 특정 인스턴스의 데이터 처리 | StatusCondition보다 세밀한 제어 가능. 데이터의 SampleState, ViewState, InstanceState 마스크와 일치하는 데이터가 있을 때만 트리거됨.15 |
| QueryCondition | 데이터의 내용 (Content) | SQL 기반 필터링 (예: temperature > 100)을 만족하는 데이터 도착 시 | ReadCondition의 특수한 형태. 미들웨어 레벨에서 데이터 내용을 미리 필터링하여 불필요한 깨어남을 방지함.6 |
| GuardCondition | 애플리케이션 사용자 정의 이벤트 | 외부 이벤트(UI 버튼 클릭, 타이머 등)와 DDS 이벤트를 통합 처리 | 사용자가 직접 set_trigger_value(TRUE)를 호출하여 트리거함. DDS 통신과 무관한 애플리케이션 이벤트를 WaitSet 루프 안에서 함께 처리할 때 필수적임.13 |
이러한 Condition 객체들은 attach_condition() API를 통해 WaitSet에 등록된다.17 중요한 점은 하나의 Condition이 여러 WaitSet에 동시에 부착될 수 있다는 것이다. 이는 하나의 이벤트(예: 데이터 도착)가 발생했을 때 여러 개의 쓰레드를 동시에 깨우는(Broadcast) 시나리오를 가능하게 하지만, 동시에 경쟁 조건(Race Condition)에 대한 주의 깊은 처리를 요구한다.5
3. 쓰레딩 모델과 동시성(Concurrency) 심층 분석
3.1 리스너(Listener) 모델: 직렬화된 실행과 한계
리스너 모델은 **‘단일 미들웨어 쓰레드’**에 의존하는 경향이 강하다. 대부분의 DDS 구현체는 도메인 참여자(DomainParticipant) 당 하나 또는 소수의 수신 쓰레드(Receive Thread)를 생성하여 네트워크 패킷을 수신하고, 역직렬화(Deserialization)한 뒤, 해당 데이터 리더의 리스너를 호출한다.2
3.1.1 직렬화(Serialization) 병목 현상
만약 하나의 도메인 참여자 내에 10개의 데이터 리더가 있고, 데이터가 동시에 쏟아진다면, 단일 수신 쓰레드는 10개의 리스너 콜백을 순차적으로(Serialized) 실행해야 한다. 이는 멀티코어 CPU가 장착된 시스템이라 하더라도, 데이터 처리는 단일 코어에서만 이루어짐을 의미한다. 결과적으로 시스템의 전체 처리량(Throughput)은 단일 코어의 성능에 의해 제한된다.2
3.1.2 미들웨어 쓰레드 차단(Blocking) 위험
리스너는 미들웨어의 내부 쓰레드 컨텍스트에서 실행되므로, 리스너 내부에서 파일 쓰기, DB 트랜잭션 등 시간이 오래 걸리는 작업을 수행하면 수신 쓰레드 자체가 멈추게 된다. 이는 하트비트(Heartbeat) 패킷 처리를 지연시켜 연결 끊김을 유발하거나, 다른 데이터 리더의 데이터 수신까지 지연시키는 연쇄적인 장애(Cascading Failure)를 일으킨다.2
3.2 WaitSet 모델: 멀티코어 병렬 처리의 극대화
WaitSet은 **‘애플리케이션 쓰레드’**를 사용하므로, 개발자가 원하는 만큼 쓰레드를 생성하여 병렬 처리를 구현할 수 있다.
3.2.1 병렬성(Parallelism) 구현
개발자는 각 데이터 리더마다 별도의 쓰레드를 생성하고, 각 쓰레드가 자신만의 WaitSet을 통해 대기하도록 설계할 수 있다. 이렇게 하면 10개의 데이터 리더에 데이터가 동시에 도착했을 때, 10개의 애플리케이션 쓰레드가 동시에 깨어나 병렬로(In Parallel) 데이터를 처리한다. 이는 멀티코어 프로세서의 자원을 온전히 활용하여 데이터 처리 속도를 획기적으로 향상시킨다.2
3.2.2 복잡한 로직의 안전한 처리
WaitSet을 통해 깨어난 애플리케이션 쓰레드는 미들웨어의 내부 락(Lock)이나 실행 컨텍스트와 분리되어 있다. 따라서 이 쓰레드에서 아무리 긴 시간이 걸리는 작업을 수행하더라도, 미들웨어의 네트워크 수신 및 관리 패킷(ACK/NACK, Heartbeat) 처리는 별도의 미들웨어 쓰레드에서 방해받지 않고 계속 수행된다. 이는 고성능 컴퓨팅이나 복잡한 데이터 융합(Sensor Fusion) 알고리즘을 수행하는 시스템에서 필수적인 요구사항이다.2
3.3 AsyncWaitSet: 비동기 처리의 진화
RTI Connext와 같은 고급 DDS 구현체는 WaitSet의 안전성과 리스너의 편의성을 결합한 AsyncWaitSet을 제공한다.21
- 쓰레드 풀(Thread Pool) 관리: AsyncWaitSet은 내부적으로 쓰레드 풀을 관리한다. 조건이 트리거되면, 개발자가 직접
wait()를 호출하는 대신, 쓰레드 풀의 워커 쓰레드(Worker Thread)가 자동으로 지정된 핸들러를 실행한다. - 리더-팔로워(Leader-Follower) 패턴: 쓰레드 풀 내부에서는 리더-팔로워 패턴을 사용하여, 하나의 쓰레드(Leader)만이 이벤트를 대기하고, 이벤트 발생 시 팔로워(Follower) 중 하나가 리더가 되며, 기존 리더는 이벤트를 처리(Process)하는 방식으로 컨텍스트 스위칭을 최적화하고 공정성(Fairness)을 보장한다.21
- 동시성 제어: 같은 조건에 대해 핸들러가 동시에 실행되지 않도록 내부적인 락을 제공하여 쓰레드 안전성(Thread Safety)을 보장한다.
4. 내부 메커니즘: Busy-Waiting vs Blocking
4.1 Busy-Waiting의 비효율성
’바쁜 대기(Busy-waiting)’는 쓰레드가 루프를 돌며 if (data_ready)... 조건을 끊임없이 확인하는 방식이다.
while (!data_ready) {
// CPU 사이클을 계속 소모하며 조건을 검사
}
이 방식은 CPU 사용률을 100%로 만들며, 운영체제 스케줄러가 다른 유용한 작업을 수행할 기회를 박탈한다. 또한 모바일 장치나 임베디드 시스템에서 배터리 소모와 발열을 급격히 증가시킨다.23
4.2 Blocking 메커니즘의 실체
WaitSet이 사용하는 **블로킹(Blocking)**은 운영체제 커널의 지원을 받는 고효율 대기 방식이다.
- 애플리케이션이
WaitSet.wait()를 호출한다. - 조건이 만족되지 않았다면, DDS 미들웨어는 OS 커널에 해당 쓰레드의 실행을 중지 요청한다 (Sleep).
- OS 스케줄러는 해당 쓰레드를 **실행 큐(Run Queue)**에서 제거하고 **대기 큐(Wait Queue)**로 이동시킨다. 이때 쓰레드의 상태는 ’Blocked’가 되며, CPU 사이클을 전혀 소모하지 않는다.24
- 데이터가 네트워크를 통해 도착하면, 인터럽트 핸들러와 미들웨어 수신 쓰레드가 데이터를 메모리에 쓰고, WaitSet과 연결된 **세마포어(Semaphore)**나 이벤트(Event) 객체에 시그널(Signal)을 보낸다.8
- OS는 시그널을 감지하고, 대기 큐에 있던 애플리케이션 쓰레드를 다시 실행 큐로 이동시킨다(Wake up).
- 쓰레드는 즉시 실행을 재개하여 데이터를 처리한다.
이러한 메커니즘을 통해 WaitSet은 데이터가 없을 때는 시스템 리소스를 전혀 사용하지 않다가, 데이터가 도착하는 순간 즉각적으로 반응하는 효율성을 달성한다. 이는 리스너와 유사해 보이지만, **‘언제 깨어날 것인가’**와 **‘어떻게 처리할 것인가’**에 대한 제어권이 애플리케이션에 있다는 점에서 근본적으로 다르다.
표 3. Busy-Waiting과 Blocking WaitSet의 성능 비교
| 특성 | Busy-Waiting | Blocking WaitSet |
|---|---|---|
| CPU 점유율 | 데이터 대기 중에도 100% 가까이 유지 23 | 대기 중에는 0%에 수렴 24 |
| 반응 속도 | 매우 빠름 (컨텍스트 스위칭 없음) | 빠름 (OS 스케줄링 및 컨텍스트 스위칭 비용 발생) |
| 멀티태스킹 영향 | 다른 프로세스의 실행을 방해함 (Starvation) | 다른 프로세스에 CPU 자원을 양보함 |
| 전력 소모 | 매우 높음 | 낮음 |
| 구현 복잡도 | 단순한 루프 구조 | OS 동기화 객체 및 미들웨어 API 활용 필요 |
5. 안전성과 신뢰성: 교착 상태(Deadlock)의 위협과 해결
5.1 리스너 모델의 치명적 위험: 교착 상태
리스너 콜백(on_data_available 등)은 미들웨어의 내부 로직이 실행되는 도중에 호출된다. 이때 미들웨어는 데이터의 일관성을 위해 엔티티(Entity)에 대한 내부 락(Mutex)을 보유하고 있을 가능성이 높다.
- 시나리오: 리스너 내부에서 애플리케이션 전용 락(App Lock)을 획득하려고 시도한다. 그런데 동시에 메인 애플리케이션 쓰레드가 이미 App Lock을 획득한 상태에서 DDS API(예:
write())를 호출하여 미들웨어 내부 락을 획득하려고 한다면? - 결과: 리스너(미들웨어 락 보유) -> App Lock 대기 vs 애플리케이션(App Lock 보유) -> 미들웨어 락 대기. 전형적인 **순환 대기(Circular Wait)**에 의한 교착 상태가 발생하여 시스템이 멈춘다.3
- 또한 리스너 내에서
write()를 호출했는데 송신 버퍼가 가득 차서 블로킹되면, 수신 쓰레드가 멈추게 되어 더 이상 데이터를 수신하지 못하고 송신 버퍼도 비워지지 않는 교착 상태가 발생할 수 있다.27
5.2 WaitSet을 통한 안전성 확보
WaitSet은 이러한 교착 상태의 위험을 구조적으로 배제한다.
wait()함수가 반환되는 시점은 이미 미들웨어의 내부 처리가 완료되고, 제어권이 애플리케이션 쓰레드로 완전히 넘어온 상태다. 미들웨어의 내부 락은 이미 해제되어 있다.- 따라서 애플리케이션 쓰레드는 자유롭게 App Lock을 획득하거나, 파일 I/O를 수행하거나, 심지어 다른 DDS
write()작업을 수행해도 교착 상태에 빠질 위험이 없다. 복잡한 로직을 수행하는 안전한 샌드박스(Sandbox)를 제공하는 셈이다.2
6. 성능 분석 및 튜닝 전략
6.1 Latency vs Throughput 트레이드오프
WaitSet은 리스너에 비해 구조적으로 **지연 시간(Latency)**이 약간 더 길 수 있다. 데이터 도착 시 미들웨어 쓰레드에서 애플리케이션 쓰레드로 **컨텍스트 스위칭(Context Switching)**이 발생하기 때문이다.2 그러나 현대의 OS와 CPU에서 이 비용은 수 마이크로초(µs) 수준이며, 대부분의 애플리케이션에서는 무시할 수 있는 수준이다.
반면 처리량(Throughput) 측면에서는 WaitSet이 더 유리하다.
- 배치 처리(Batching): 고속으로 데이터가 유입될 때, 리스너는 패킷마다 한 번씩 호출되지만, WaitSet은 한 번 깨어났을 때 큐에 쌓인 수십, 수백 개의 데이터를
take()API를 통해 한꺼번에 가져와 처리할 수 있다. 이는 함수 호출 오버헤드와 락 경합(Lock Contention)을 줄여 전체적인 처리량을 극대화한다.2
6.2 튜닝 파라미터 활용
RTI Connext 등의 구현체에서는 max_event_count와 같은 속성을 통해 WaitSet의 동작을 튜닝할 수 있다.2
max_event_count: 한 번의wait()호출에서 반환할 최대 이벤트(Condition) 수를 제한하여, 너무 많은 이벤트를 한 번에 처리하느라 응답성이 떨어지는 것을 방지하거나, 반대로 한 번에 최대한 많이 처리하여 효율을 높이는 조절이 가능하다.wait()의 타임아웃 설정을 통해 주기적인 작업(Heartbeat 체크)과 이벤트 기반 작업을 혼합한 하이브리드 루프를 구성할 수도 있다.18
7. 결론
분석을 종합하면, 리스너가 ‘미들웨어 쓰레드를 빌려 쓰는’ 가볍고 즉각적인 방식이라면, WaitSet은 ‘애플리케이션 쓰레드를 효율적으로 운용하여 시스템의 제어권을 확보하는’ 강력하고 안전한 방식이다.
WaitSet은 다음과 같은 상황에서 필수적으로 선택되어야 한다:
- 안전성이 중요한 미션 크리티컬 시스템: 교착 상태의 위험을 원천 차단해야 할 때.
- 고성능 병렬 처리: 멀티코어 CPU를 활용하여 대량의 데이터를 병렬로 처리해야 할 때.
- 복잡한 데이터 처리: 데이터 수신 후 DB 저장, 복잡한 연산 등 긴 시간이 소요되는 작업을 수행해야 할 때.
- 다중 이벤트 복합 처리: 여러 토픽의 데이터 도착, QoS 이벤트, 외부 신호 등을 하나의 루프에서 통합적으로 관리해야 할 때.
비록 리스너에 비해 약간의 지연 시간이 추가되고 구현 코드가 다소 길어질 수 있으나, WaitSet이 제공하는 예측 가능성(Predictability), 안전성(Safety), **확장성(Scalability)**은 현대적인 실시간 분산 시스템 구축에 있어 대체 불가능한 가치를 지닌다. 따라서 단순한 상태 모니터링을 제외한 대부분의 실질적인 데이터 처리 로직에는 WaitSet 패턴을 적용하는 것이 DDS 시스템 설계의 모범 사례(Best Practice)라 할 수 있다.
8. 참고 자료
- NDDS Latency and Throughput - DDS Foundation, https://www.dds-foundation.org/sites/default/files/Evaluating_Performance_Publish_Subscribe_Platforms.pdf
- Use WaitSets, Except When You Need Extreme Latency | Data …, https://community.rti.com/best-practices/use-waitsets-except-when-you-need-extreme-latency
- Package: DDS.Listener - RTI Community, https://community.rti.com/static/documentation/connext-dds/current/doc/api/connext_dds/api_ada/dds-listener.ads.html
- C++ API, 0.11.0 - Waitsets — Eclipse Cyclone DDS, https://cyclonedds.io/docs/cyclonedds-cxx/latest/howtos/waitset.html
- Package: DDS.WaitSet - RTI Community, https://community.rti.com/static/documentation/connext-dds/7.3.0/doc/api/connext_dds/api_ada/dds-waitset.ads.html
-
- Reading and Writing Data — The Data Distribution Service Tutorial, https://download.zettascale.online/www/docs/Vortex/html/ospl/DDSTutorial/readandwrite.html
- waiting on multiple filedescriptor/objects - RTI Community, https://community.rti.com/forum-topic/waiting-multiple-filedescriptorobjects
- RTI Connext Micro, https://community.rti.com/static/documentation/connext-micro/current/doc/RTI_ConnextDDSMicro_UsersManual.pdf
- Difference between Windows select and Unix select for nonblocking …, https://stackoverflow.com/questions/19095012/difference-between-windows-select-and-unix-select-for-nonblocking-sockets
- poll vs select vs event-based, https://daniel.haxx.se/docs/poll-vs-select.html
- RTI Connext DDS Micro C++ API: OSAPI AutoSAR, https://community.rti.com/static/documentation/connext-micro/2.4.14/doc/api_cpp/html/group__OSAPI__AutosarClass.html
- Linux – IO Multiplexing – Select vs Poll vs Epoll - Developers Area, https://devarea.com/linux-io-multiplexing-select-vs-poll-vs-epoll/
- Waitsets | Data Distribution Service (DDS) Community RTI Connext …, https://community.rti.com/examples/waitsets
- 3.1.4. Conditions and Wait-sets - eProsima Fast DDS, https://fast-dds.docs.eprosima.com/en/v2.6.10/fastdds/dds_layer/core/waitsets/waitsets.html
- CoreDX DDS Modern C++ API: Conditions and WaitSets, https://www.twinoakscomputing.com/documents/refman_html_5.0/CoreDX_DDS_CXX_Reference_5.0/cookbook_conditions.html
- Conditions and Listeners - OpenDDS 3.34.0-dev, https://opendds.readthedocs.io/en/latest/devguide/conditions_and_listeners.html
- 20.1.1.1.5. Wait-set - 3.4.1 - Fast DDS, https://fast-dds.docs.eprosima.com/en/3.x/fastdds/api_reference/dds_pim/core/condition/waitset.html
- dds::core::cond::WaitSet Class Reference - RTI Community, https://community.rti.com/static/documentation/connext-dds/7.4.0/doc/api/connext_dds/api_cpp2/classdds_1_1core_1_1cond_1_1WaitSet.html
- Thread Pool Design Considerations - Ice, https://doc.zeroc.com/ice/3.7/client-server-features/the-ice-threading-model/thread-pool-design-considerations
- Exploiting Multi-Core with CoreDX Data Distribution Service, https://www.twinoakscomputing.com/wp/CoreDX_DDS_Multicore.pdf
- RTI Connext C API: AsyncWaitSet, https://community.rti.com/static/documentation/connext-dds/6.1.2/doc/api/connext_dds/api_c/group__DDSAsyncWaitSetModule.html
- RTI Connext Traditional C++ API: DDSAsyncWaitSet Class Reference, https://community.rti.com/static/documentation/connext-dds/7.3.0/doc/api/connext_dds/api_cpp/classDDSAsyncWaitSet.html
- Is busy waiting always less efficient (in terms of using processor time …, https://www.gauthmath.com/solution/9iXeXs_BvdA/Is-busy-waiting-always-less-efficient-in-terms-of-using-processor-time-than-a-bl
- Avoiding Busy Waiting in Java - Java Code Geeks, https://www.javacodegeeks.com/avoiding-busy-waiting-in-java.html
- what’s different between the Blocked and Busy Waiting?, https://stackoverflow.com/questions/26541119/whats-different-between-the-blocked-and-busy-waiting
- Don’t Help Your Clients to Deadlock - Vasil Kosturski, https://vkontech.com/dont-help-your-clients-to-deadlock/
- RTI Connext Modern C++ API: Listener Class Reference, https://community.rti.com/static/documentation/connext-dds/current/doc/api/connext_dds/api_cpp2/classListener.html
- How to implement a Thread-Safe data structure in C++, https://levelup.gitconnected.com/understanding-multithreading-and-thread-safe-data-structure-in-c-04981f21edf5