C++ 라이브러리 / 스레드 타이머

스레드 타이머는 주 프로그램이 실행되는 메인 스레드와 별개의 스레드에서 일정한 주기마다 주어진 콜백함수를 실행시키는 타이머입니다. C#에선 System.Threading.Timer로 제공하고 있지만 C++에선 직접 만들어 써야 합니다.

STL의 conditional_variable::wait_for()을 통해 매번 루프를 돌리는 풀링을 사용하지 않고 CPU를 자원을 거의 소모하지 않는 타이머를 만들 수 있습니다. 다만 아래 구현에선 타이머 한 개 당 스레드도 한 개가 필요합니다. 그래서 타이머를 너무 많이 만들면 스레드의 스택을 할당하는 과정에서 메모리가 많이 소모될 수 있습니다. 타이머를 많이 만들기 위해 정렬 큐로 여러 타이머를 하나의 스레드에 묶는 방법도 있습니다. 대신에 한 타이머 이벤트 처리가 늦어지면 다른 이벤트도 처리도 같이 늦어집니다. 이런 구현은 다음에 만들어 보도록 하겠습니다.

thread_timer.hpp
#pragma once

#include <thread>
#include <functional>
#include <condition_variable>
#include <mutex>
#include <chrono>

class ThreadTimer {
public:
	enum Status {
		Stop       = 0,
		Start      = 1,
		Start_Once,
		Destroyed
	};

	inline ThreadTimer()
		: ThreadTimer(std::chrono::milliseconds(1000), {}) {}

	template <class Rep, class Period>
	inline ThreadTimer(std::chrono::duration<Rep, Period> interval, std::function<void()> callback, bool start = false)
		: status((Status)start), callback(callback), thread(timer_impl, this) {
		setInterval(interval);
	}

	inline ~ThreadTimer() {
		status = Destroyed;
		cd.notify_one();
		thread.join();
	}

	inline Status getStatus() const {
		return status;
	}

	template <class Rep, class Period>
	inline void setInterval(std::chrono::duration<Rep, Period> interval) {
		std::lock_guard<std::mutex> guard(mutex_params);
		this->interval = interval;
		if (status != Stop) cd.notify_one();
	}

	inline void setCallback(std::function<void()> callback) {
		std::lock_guard<std::mutex> guard(mutex_params);
		this->callback = callback;
	}

	inline void start() {
		std::lock_guard<std::mutex> guard(mutex_params);
		status = Start;
		cd.notify_one();
	}

	inline void start_once() {
		std::lock_guard<std::mutex> guard(mutex_params);
		status = Start_Once;
		cd.notify_one();
	}

	inline void stop() {
		std::lock_guard<std::mutex> guard(mutex_params);
		status = Stop;
		cd.notify_one();
	}

private:
	static void timer_impl(ThreadTimer* timer) {
		auto& mutex   = timer->mutex_cd;
		auto& cd      = timer->cd;
		auto& status  = timer->status;
		
		while (status != Destroyed) {
			if (status == Start || status == Start_Once) {
				std::unique_lock<std::mutex> lock(mutex);
				auto res = cd.wait_for(lock, timer->interval);
				if (res == std::cv_status::no_timeout) continue;

				timer->callback();
				if (status == Start_Once) status = Stop;
			} else if (status == Stop) {
				std::unique_lock<std::mutex> lock(mutex);
				cd.wait(lock); // idle
			}
		}
	}

private:
	Status                   status;
	std::chrono::nanoseconds interval;
	std::function<void()>    callback;
	std::mutex               mutex_params;
	std::mutex               mutex_cd;
	std::condition_variable  cd;
	std::thread              thread;
};
C++

아래는 실행 예제입니다.

demo.cpp
#include <iostream>
#include "thread_timer.hpp"

using namespace std;

int main() {
	int cnt = 0;
	
	ThreadTimer t0(1000ms, [&](){ 
		cout << "1000ms Elapsed!, cnt = " << ++cnt << "\n"; 
		});

	t0.start();
 
	// wait until cnt == 10
	while (cnt < 10) {
		// do something...
	}

	return 0;
}
C++

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다