# Boost.Asio를 활용한 비동기 타이머

#### 비동기 타이머의 개념

Boost.Asio는 네트워크 및 기타 비동기 작업을 수행할 수 있는 강력한 라이브러리이며, 이 중에서 비동기 타이머는 중요한 역할을 한다. 비동기 타이머는 비동기 작업을 일정 시간 후에 실행하거나 주기적으로 작업을 수행할 수 있도록 해준다. 여기서는 **boost::asio::steady\_timer** 클래스를 사용하여 타이머를 설정하고, 이를 통해 비동기 작업을 처리하는 과정을 살펴본다.

비동기 타이머는 동기 타이머와 달리 특정 시간이 지나기 전까지 해당 스레드를 블로킹(blocking)하지 않는다. 이를 통해 여러 비동기 작업을 효율적으로 처리할 수 있으며, 이벤트 기반의 프로그램에서 흔히 사용된다.

#### 타이머 설정 및 비동기 처리

**boost::asio::steady\_timer**를 사용하여 비동기 타이머를 설정할 때는 다음의 과정이 필요하다:

1. **io\_context 생성**: 모든 비동기 작업의 중심에는 **boost::asio::io\_context** 객체가 있다. 이 객체는 비동기 작업의 실행을 관리하며, 타이머 또한 이 컨텍스트 내에서 동작한다.

   ```cpp
   boost::asio::io_context io;
   ```
2. **steady\_timer 생성**: 타이머 객체를 생성할 때, 기본적으로 **boost::asio::io\_context**와 특정 시간을 받아 설정된다. 여기서 시간은 **boost::asio::chrono::duration**을 사용하여 지정한다.

   ```cpp
   boost::asio::steady_timer timer(io, boost::asio::chrono::seconds(5));
   ```
3. **비동기 대기**: 타이머는 설정된 시간 동안 비동기적으로 대기하고, 시간이 경과한 후 콜백 함수가 호출된다. 이때 콜백 함수는 타이머의 완료 시점에 호출된다.

   ```cpp
   timer.async_wait([](const boost::system::error_code& error) {
       if (!error) {
           std::cout << "Timer expired!" << std::endl;
       }
   });
   ```

#### 비동기 타이머의 콜백 구조

비동기 타이머의 핵심은 **async\_wait** 함수에 전달된 콜백 함수가 시간 경과 후에 호출된다는 점이다. 이 콜백 함수는 보통 **std::function** 또는 **lambda**로 작성되며, 첫 번째 인자로 **boost::system::error\_code**가 전달된다. 이 코드는 타이머가 정상적으로 동작했는지 여부를 나타낸다.

**수학적으로 비동기 타이머의 개념을 표현하면 다음과 같다.**

시간 $t$ 후에 특정 작업 $f(t)$가 호출되며, 여기서 $t$는 타이머의 대기 시간이다. 즉, 타이머의 작동은 함수 $f$가 비동기적으로 호출되는 이벤트를 트리거하는 것으로 정의할 수 있다. 이를 수식으로 표현하면:

$$
\text{async\_wait}(t) = f(t) \quad \text{where} \quad t \in \mathbb{R}^{+}
$$

위의 수식에서 $t$는 양의 실수로 대기 시간을 나타내며, $f(t)$는 시간이 경과된 후 호출되는 함수다. 타이머가 동작하는 동안 프로그램의 다른 부분은 영향을 받지 않으며, **io\_context**에 의해 관리된다.

#### 타이머를 이용한 작업 흐름

비동기 타이머는 다음의 순서로 작동한다:

1. 타이머가 설정된다.
2. 타이머가 대기 상태에 들어간다.
3. 설정된 시간이 지나면 **async\_wait**에 등록된 콜백 함수가 호출된다.

이 흐름은 이벤트 기반 프로그램에서 매우 중요하다. 대기하는 동안 프로그램의 다른 부분이 중단되지 않으며, 설정된 시간이 지나면 즉시 타이머의 콜백이 호출된다.

#### 동기 타이머와의 차이점

동기 타이머는 **steady\_timer.wait()** 메소드를 사용하여 구현할 수 있으며, 이는 특정 시간이 지나기 전까지 해당 스레드를 블로킹한다. 비동기 타이머는 \*\*async\_wait()\*\*을 통해 스레드를 블로킹하지 않고 대기하며, 설정된 시간이 지나면 콜백을 통해 작업이 처리된다. 이를 수학적으로 표현하면 다음과 같다:

동기 타이머:

$$
\text{wait}(t) \rightarrow \text{block} \quad \forall t
$$

비동기 타이머:

$$
\text{async\_wait}(t) \rightarrow \text{non-blocking} \quad \forall t
$$

여기서 **blocking**은 작업이 완료될 때까지 스레드가 중단되는 것을 의미하며, **non-blocking**은 타이머가 대기 중일 때도 다른 작업을 계속 수행할 수 있음을 의미한다.

#### 비동기 타이머의 재설정

비동기 타이머는 한 번 설정된 후 시간이 지나면 만료되지만, 타이머를 재설정하여 새로운 대기 시간을 지정할 수 있다. 예를 들어, 주기적인 작업을 수행하려면 타이머를 계속해서 재설정하는 방식으로 구현할 수 있다.

타이머를 재설정하는 방법은 **expires\_after** 또는 **expires\_at** 함수를 사용하는 것이다. **expires\_after**는 현재 시점으로부터 일정 시간이 지난 후 타이머가 만료되도록 설정하고, **expires\_at**은 특정 시점을 지정하여 타이머를 설정한다.

```cpp
timer.expires_after(boost::asio::chrono::seconds(5));  // 5초 후에 만료
timer.async_wait([](const boost::system::error_code& error) {
    if (!error) {
        std::cout << "Timer expired and reset!" << std::endl;
    }
});
```

이 코드는 타이머가 만료된 후 5초마다 다시 타이머를 설정하여 주기적인 비동기 작업을 수행하는 예시이다. 이를 수식으로 표현하면 타이머가 재설정되는 주기적 동작을 다음과 같이 나타낼 수 있다:

$$
t\_0, t\_1, t\_2, \dots, t\_n \quad \text{where} \quad t\_{i+1} = t\_i + \Delta t
$$

여기서 $t\_0$은 처음 타이머가 만료되는 시간, $\Delta t$는 주기적인 시간 간격, 그리고 $t\_{i+1}$은 타이머가 재설정되는 시점을 의미한다.

#### 주기적인 타이머 작업 흐름

주기적인 타이머는 기본적으로 타이머가 만료될 때마다 다시 타이머를 재설정하여, 주기적인 작업을 수행할 수 있다. 이를 프로그램 흐름으로 나타내면 아래와 같은 순서로 동작한다:

1. 타이머가 설정되고 대기 상태에 진입.
2. 시간이 경과하여 타이머 만료.
3. 타이머 만료 후 콜백 함수가 호출됨.
4. 콜백 함수 내에서 타이머가 다시 재설정됨.
5. 새로운 시간 동안 다시 대기.

이러한 흐름은 **loop** 구조로 표현될 수 있으며, 주기적인 비동기 작업을 가능하게 한다. 아래의 다이어그램은 주기적인 타이머 작업의 흐름을 설명한다:

{% @mermaid/diagram content="graph TD
A\[타이머 설정] --> B\[타이머 대기]
B --> C\[타이머 만료]
C --> D\[콜백 함수 실행]
D --> A\[타이머 재설정]" %}

#### 타이머 취소

타이머는 특정 시점에서 더 이상 필요하지 않거나, 어떤 조건이 충족되었을 때 취소될 수 있다. 타이머 취소는 **cancel()** 함수를 사용하여 이루어진다. 타이머가 취소되면, 해당 타이머에 등록된 비동기 작업은 즉시 종료되며, **async\_wait**에 등록된 콜백 함수는 호출되지 않는다. 그러나 취소되었을 때는 콜백 함수가 호출되더라도, **boost::system::error\_code** 객체는 **boost::asio::error::operation\_aborted** 상태로 설정된다.

```cpp
timer.cancel();  // 타이머 취소
```

타이머 취소에 대한 수학적 표현은 타이머가 설정된 시점에서 취소된 시점까지의 시간 간격을 나타낼 수 있다. 만약 타이머가 $t\_{\text{set}}$에서 설정되고, $t\_{\text{cancel}}$에서 취소되었다면, 타이머는 다음과 같은 불등식을 만족한다:

$$
t\_{\text{set}} < t\_{\text{cancel}} < t\_{\text{expire}}
$$

여기서 $t\_{\text{expire}}$는 타이머가 만료되었을 시간을 나타낸다.

#### 타이머와 멀티스레딩

Boost.Asio는 비동기 작업을 여러 스레드에서 실행할 수 있도록 지원한다. **io\_context** 객체는 여러 스레드에서 호출될 수 있으며, 비동기 타이머는 이러한 환경에서도 잘 동작한다. 그러나 주의할 점은 하나의 타이머에 여러 스레드가 동시에 접근할 수 있다는 것이다. 이 경우, 타이머를 재설정하거나 취소할 때 데이터 경쟁(data race)이 발생하지 않도록 적절한 동기화가 필요하다.

**boost::asio::io\_context::run()** 메소드를 여러 스레드에서 호출하여 비동기 작업을 병렬로 실행할 수 있다. 이 경우 타이머 작업이 여러 스레드에서 동시다발적으로 실행될 수 있지만, 각 스레드는 독립적으로 관리된다.

```cpp
std::vector<std::thread> threads;
for (int i = 0; i < 4; ++i) {
    threads.emplace_back([&io]() {
        io.run();
    });
}

for (auto& t : threads) {
    t.join();
}
```

위 코드는 4개의 스레드에서 **io\_context**의 **run** 메소드를 실행하는 예시이다. 이 방법을 통해 비동기 타이머가 멀티스레드 환경에서도 효과적으로 동작할 수 있다.

#### 타이머와 작업 큐

비동기 타이머는 여러 비동기 작업을 큐에 쌓아 놓고 순차적으로 처리할 수 있는 강력한 도구이다. **io\_context**는 작업 큐를 관리하며, 타이머를 비롯한 모든 비동기 작업을 큐에 저장한 뒤, 이들을 하나씩 실행한다. 작업 큐는 **FIFO(First In, First Out)** 방식으로 관리되며, 타이머가 만료되면 그에 대응하는 작업이 실행된다.

이 큐의 동작을 수식으로 표현하면, **N**개의 비동기 작업을 처리하는 경우 작업 큐는 다음과 같이 정의될 수 있다:

$$
\mathbf{Q} = { f\_1, f\_2, f\_3, \dots, f\_N }
$$

여기서 $f\_i$는 i번째 비동기 작업(예: 타이머의 콜백 함수)을 의미하며, **io\_context**는 이 작업들을 순서대로 실행한다. 타이머의 비동기 작업이 실행되면, 작업 큐에서 해당 작업이 제거되고, 다음 작업이 대기 상태로 넘어간다.

#### 타이머와 동시성 제어

비동기 타이머가 멀티스레드 환경에서 실행될 때, 여러 타이머가 동일한 **io\_context**를 공유할 수 있다. 이때 타이머는 동시성 제어가 필요할 수 있으며, 특히 여러 스레드가 타이머에 접근하거나 작업을 동시에 실행하려고 할 때 동기화가 필요하다.

Boost.Asio에서는 다음과 같은 방식으로 동시성을 제어한다:

1. **strand** 사용: **strand**는 **io\_context** 내에서 특정 작업들이 순차적으로 실행되도록 보장해준다. 여러 스레드가 동일한 타이머에 접근할 때 **strand**를 사용하면, 데이터 경합(data race) 문제를 방지할 수 있다. 이는 특히 타이머를 재설정하거나 취소할 때 유용하다.

```cpp
boost::asio::strand<boost::asio::io_context::executor_type> strand(io.get_executor());

boost::asio::steady_timer timer(io, boost::asio::chrono::seconds(5));

timer.async_wait(strand.wrap([](const boost::system::error_code& error) {
    if (!error) {
        std::cout << "Timer expired within strand!" << std::endl;
    }
}));
```

위 코드에서 **strand.wrap()** 메소드는 타이머의 콜백 함수가 다른 스레드와 동기화되어 순차적으로 실행되도록 보장한다. 이때 여러 스레드가 타이머에 접근하더라도, 동시성 문제는 발생하지 않는다.

#### 주기적인 비동기 타이머 예제

비동기 타이머를 활용하여 주기적으로 작업을 수행하는 간단한 예제를 살펴보자. 타이머는 만료될 때마다 자신을 재설정하여 주기적인 동작을 수행한다. 이는 매우 일반적인 패턴이며, 네트워크 요청의 주기적 전송, 상태 체크, 주기적 로그 파일 작성 등에서 활용될 수 있다.

```cpp
boost::asio::io_context io;

boost::asio::steady_timer timer(io, boost::asio::chrono::seconds(1));

std::function<void(const boost::system::error_code&)> handler;

handler = [&](const boost::system::error_code& error) {
    if (!error) {
        std::cout << "Timer expired. Resetting..." << std::endl;
        timer.expires_after(boost::asio::chrono::seconds(1));
        timer.async_wait(handler);  // 타이머를 다시 설정하여 주기적으로 실행
    }
};

timer.async_wait(handler);

io.run();
```

이 예제에서는 타이머가 만료될 때마다 스스로를 다시 설정하여, 1초마다 **handler** 함수가 호출되는 구조이다. **handler** 함수는 재귀적으로 호출되며, 비동기 작업을 끊임없이 이어갈 수 있다.

수학적으로 이 동작을 표현하면, $t\_0, t\_1, t\_2, \dots$ 시간 시점에서 주기적으로 작업이 실행됨을 나타낸다. 타이머가 만료되는 주기를 $\Delta t$라 하면, 다음과 같은 점화식으로 주기적인 타이머 작업의 시점을 표현할 수 있다:

$$
t\_{i+1} = t\_i + \Delta t \quad \text{where} \quad i \in \mathbb{N}
$$

#### 비동기 타이머의 예외 처리

비동기 작업 중에는 다양한 이유로 인해 예외가 발생할 수 있다. 특히 네트워크 연결이 끊기거나, 시스템 자원이 부족한 경우, 타이머가 정상적으로 동작하지 않을 수 있다. 이를 처리하기 위해서는 **boost::system::error\_code**를 통해 에러를 감지하고, 적절한 예외 처리를 수행해야 한다.

```cpp
boost::asio::steady_timer timer(io, boost::asio::chrono::seconds(5));

timer.async_wait([](const boost::system::error_code& error) {
    if (error) {
        if (error == boost::asio::error::operation_aborted) {
            std::cout << "Timer operation aborted." << std::endl;
        } else {
            std::cerr << "Error: " << error.message() << std::endl;
        }
    } else {
        std::cout << "Timer expired successfully!" << std::endl;
    }
});
```

이 코드는 타이머가 취소되었을 때와, 그 외의 에러가 발생했을 때 각각의 상황을 구분하여 처리하는 예시이다. 에러가 발생했을 때 **boost::system::error\_code**의 상태를 확인하여 적절한 조치를 취할 수 있다.

에러 처리에 대한 수식적 표현은 다음과 같다. 타이머가 실행될 때 에러가 발생하는지 여부를 $\epsilon$이라고 하면, 에러 발생 여부는 다음과 같이 이진 함수로 정의할 수 있다:

$$
\epsilon(t) = \begin{cases} 1 & \text{에러 발생 시} \ 0 & \text{정상 작동 시} \end{cases}
$$

여기서 $\epsilon(t) = 1$인 경우, 타이머 작업은 비정상 종료되며, 에러 핸들링 루틴이 실행된다.
