strand를 이용, completion handler를 multithreaded program에서 동기화하는 방법을 알아보자.
completion handler는 io_context::run()을 실행한 스레드에서 실행된다. 하나의 스레드에서만 run()을 호출 했다면, 핸들러는 절대 동시에 실행되지 않는다.
싱글 스레드에서만 asio를 사용하여 앱을 만드는 것은 좋지만 다음과 같은 제약이 있다.
- 핸들러 작업이 오래걸리면, 반응성이 느려짐.
- 멀티 프로세스 활용 못함.
대안으로 io_context::run()을 콜하는 스레드 풀을 만드는 방법이 있다.
그러나, 핸들러에서 공유 자원에 접근하는 등 스레드 safe하지 않기 때문에 동기화 작업이 필요하다.
io_context를 받아 멤버변수 strand를 초기화 하는 클래스를 만든다.
class Printer
{
public:
Printer(boost::asio::io_context& io)
: strand_(boost::asio::make_strand(io)),
timer1_(io, boost::asio::chrono::seconds(1)),
timer2_(io, boost::asio::chrono::seconds(1)),
count_(0)
{
timer1_.async_wait(boost::asio::bind_executor(strand_,
boost::bind(&Printer::print1, this)));
timer2_.async_wait(boost::asio::bind_executor(strand_,
boost::bind(&Printer::print2, this)));
}
...
}
strand class는 executor adaptor이다.
이를 이용 handler를 dispatch하면 handler 실행은 다음 핸들러가 실행 되기전에 완료된다.
다수의 스레드가 io_context::run()을 호출하여도 핸들러의 동기화를 보장한다.
물론, strand로 dispatch 하지 않은 핸들러나 다른 strand object로 dispatch하면 다른 스레드에서 동시에 실행된다.
...
~Printer()
{
std::cout << "Final count is " << count_ << std::endl;
}
void print1()
{
if (count_ < 100)
{
std::cout << "Timer 1: " << count_ << std::endl;
++count_;
timer1_.expires_at(timer1_.expiry() + boost::asio::chrono::seconds(1));
timer1_.async_wait(boost::asio::bind_executor(strand_,
boost::bind(&Printer::print1, this)));
/*timer1_.async_wait(
boost::bind(&Printer::print1, this));*/
}
}
void print2()
{
if (count_ < 100)
{
std::cout << "Timer 2: " << count_ << std::endl;
++count_;
timer2_.expires_at(timer2_.expiry() + boost::asio::chrono::seconds(1));
timer2_.async_wait(boost::asio::bind_executor(strand_,
boost::bind(&Printer::print2, this)));
/*timer2_.async_wait(
boost::bind(&Printer::print2, this));*/
}
}
private:
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
boost::asio::steady_timer timer1_;
boost::asio::steady_timer timer2_;
int count_;
};
int main()
{
boost::asio::io_context io;
Printer p(io);
boost::thread t(boost::bind(&boost::asio::io_context::run, &io));
io.run();
t.join();
return 0;
}
싱글 스레드에서와 마찬가지로, io_context::run() 실행하는 스레드 모두, 실행할 work가 없으면 종료한다.
참조 : Timer.5 - Synchronising completion handlers in multithreaded programs - 1.79.0 (boost.org)
DllMain 정리 (0) | 2022.08.24 |
---|---|
Strand 정리 (0) | 2022.08.23 |
[boost] asio #1 (0) | 2022.05.01 |
[Visual Studio] Character Set Unicode vs MBCS (0) | 2022.04.26 |
enable_shared_from_this (0) | 2022.04.22 |
댓글 영역