C++ 시간·타이머·세마포어 유틸리티 지도: 벤치마크, 주기 작업 드리프트, counting_semaphore, 날짜 서식

이 글의 핵심

주기 작업을 sleep_for로 만들면 시간이 누적해서 밀립니다. 50번 반복 실험에서 기대 1150ms가 sleep_for 방식은 1342ms, sleep_until 방식은 1153ms였습니다. counting_semaphore로 동시 실행 수를 3개로 제한한 결과와 날짜 서식 결과까지 정리하고 관련 글을 연결했습니다.

시간을 재고, 주기적으로 실행하고, 동시에 몇 개만 돌게 제한하는 일은 서버와 로봇 코드에서 자주 쓴다. 이 글은 관련 도구를 한 번에 실행해 확인하고, 자세한 글로 연결한다. 환경은 Apple clang 21.0.0, macOS arm64, -std=c++20이다. 시간 측정은 한 번씩 실행한 값이라 실행마다 다를 수 있다.

실행으로 확인한 결과

항목결과
23ms 주기 x 50회, 기대 총 시간 1,150mssleep_for(20ms) + 작업 3ms 방식 1,342ms
같은 주기를 sleep_until(next += 23ms)로1,153ms
counting_semaphore<8> sem(3), 스레드 10개, 작업 10ms동시 실행 최대 3
format("{:%Y-%m-%d %H:%M:%S}", sys_days{2026y/November/10} + 14h + 5min + 9s)2026-11-10 14:05:09
year_month_day{2026y/2/29}.ok()0(유효하지 않은 날짜)
format("{}", duration_cast<milliseconds>(1500ms + 250us))1500ms

주기 작업은 왜 밀리나

sleep_for(20ms)는 “지금부터 20ms 쉰다”는 뜻이라, 작업이 3ms 걸리면 한 주기가 23ms보다 길어진다. 실험에서 50번 반복했을 때 기대보다 약 192ms가 더 걸렸다. 한 번에 약 3.8ms씩 쌓인 셈이다. 같은 작업을 sleep_until로, 다음 실행 시각을 next += 23ms로 누적해서 계산하자 총 1,153ms로 기대와 3ms 차이였다. 짧은 sleep의 부정확함과 드리프트를 더 자세히 다룬 글이 sleep_for·sleep_until 주기 작업이다.

실행 시간 측정은 어떻게 하나

벤치마크용 시계는 steady_clock을 쓰고, 한 번의 측정값을 믿지 않고 여러 번 돌려 분포를 본다. 이 글의 위 실험도 한 번씩만 돌린 값이라 방향을 보는 용도다. 스톱워치 클래스를 만들고 측정하는 방법은 chrono 스톱워치와 벤치마크에 있다.

동시 실행 수 제한

세마포어는 “동시에 N개까지만”을 표현하는 가장 직접적인 도구다. 스레드 10개가 모두 acquire()를 부르고 3개만 통과하도록 했을 때, 실행 중인 수의 최댓값을 원자 변수로 기록하니 3이었다. 더 많은 스레드가 한꺼번에 들어와도 4개 이상이 동시에 실행되지는 않았다. binary_semaphore, 뮤텍스와의 차이, 소유자 개념이 없다는 점은 C++20 세마포어 글에서 설명한다.

날짜와 시간 서식

std::format의 chrono 서식(%Y-%m-%d %H:%M:%S)으로 2026-11-10 14:05:09를 얻었다. 존재하지 않는 날짜(2026-02-29)는 ok()가 거짓이었다. 입력 문자열을 파싱하는 chrono::parse와 zoned_time, 서식 지정 전체는 C++20 날짜 파싱과 서식에, 지역별 숫자·통화·날짜 표기는 std::locale과 facet에 있다.

확인하지 못한 것

  • chrono::parse와 zoned_time을 이 환경에서 실행하지는 않았다.
  • 여러 번 반복한 평균과 분산. 시간 값은 한 번씩 측정했다.
  • 다른 OS와 컴파일러에서의 sleep 정밀도.