C++ 将异步定时器提升到 运行 与程序并行
C++ boost asynchronous timer to run in parallel with program
注意:这是针对 C++98
我正在尝试开发一个在我的主程序后台运行的简单 timer/counter。
我以前没有使用过异步定时器,我一直在尝试按照 boost 教程学习如何做到这一点,但它们似乎仍然阻碍了我的主要功能。我从 boost 网站稍微修改了 Timer.3 以进行实验。
本质上,下面的程序我想做的是:
- 运行 主要
- 执行测试运行() 计数到 5
- 同时test运行()在计数,在main中打印"TEST ABC"。
main.cpp
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
void print(const boost::system::error_code& /*e*/, boost::asio::deadline_timer* t, int* count)
{
if (*count < 5)
{
std::cout << *count << std::endl;
++(*count);
t->expires_at(t->expires_at() + boost::posix_time::seconds(1)); // every 1 second advance
t->async_wait(boost::bind(print, boost::asio::placeholders::error, t, count));
}
std::cout << " PRINT " << std::endl;
}
void testRun()
{
boost::asio::io_service io;
int count = 0;
boost::asio::deadline_timer t(io, boost::posix_time::seconds(2)); // start io object (function) after 2 seconds.
t.async_wait(boost::bind(print, boost::asio::placeholders::error, &t, &count));
io.run();
std::cout << "Final count is " << count << std::endl;
}
int main()
{
testRun();
std::cout << " TEST ABC " << std::endl;
return 0;
}
输出
0
PRINT
1
PRINT
2
PRINT
3
PRINT
4
PRINT
PRINT
Final count is 5
TEST ABC
我希望我的输出看起来像:
TEST ABC
0
PRINT
1
PRINT
2
PRINT
3
PRINT
4
PRINT
PRINT
Final count is 5
为了解构手头的任务,我将从一个简单的 C++98 实现开始。
我们会将其清理为现代 C++,然后替换为 Asio。
您会发现 Asio 不需要线程,这很好。但是我们必须回到过去,用 C++98 替换现代 C++。
最后,您将了解加入现代 C++ 的所有原因,以及如何组织代码以轻松管理复杂性。
C++98
下面是我在 c++98 中的写法:
#include <pthread.h>
#include <iostream>
#include <sstream>
#include <unistd.h>
static pthread_mutex_t s_mutex = {};
static bool s_running = true;
static bool is_running(bool newvalue) {
pthread_mutex_lock(&s_mutex);
bool snapshot = s_running;
s_running = newvalue;
pthread_mutex_unlock(&s_mutex);
return snapshot;
}
static bool is_running() {
pthread_mutex_lock(&s_mutex);
bool snapshot = s_running;
pthread_mutex_unlock(&s_mutex);
return snapshot;
}
static void output(std::string const& msg) {
pthread_mutex_lock(&s_mutex);
std::cout << msg << "\n";
pthread_mutex_unlock(&s_mutex);
}
static void* count_thread_func(void*) {
for (int i = 0; i < 5; ++i) {
::sleep(1);
std::ostringstream oss;
oss << "COUNTER AT " << (i+1);
output(oss.str());
}
is_running(false);
return NULL;
}
int main() {
pthread_t thr = {0};
pthread_create(&thr, NULL, &count_thread_func, NULL);
while (is_running()) {
::usleep(200000);
output("TEST_ABC");
}
pthread_join(thr, NULL);
}
版画
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 1
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 2
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 3
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 4
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 5
TEST_ABC
C++11
好吧,上面的内容几乎不是 C++。它实际上是 "same" 但在 C 中使用 printf
更方便。以下是 C++11 的改进方式:
- std::thread,std::atomic_bool,std::chono,std::this_thread,std::to_string,std::mutex/lock_guard,更好的初始化。
#include <thread>
#include <iostream>
#include <chrono>
#include <mutex>
#include <atomic>
using std::chrono::milliseconds;
using std::chrono::seconds;
static std::mutex s_mutex;
static std::atomic_bool s_running {true};
static void output(std::string const& msg) {
std::lock_guard<std::mutex> lk(s_mutex);
std::cout << msg << "\n";
}
static void count_thread_func() {
for (int i = 0; i < 5; ++i) {
std::this_thread::sleep_for(seconds(1));
output("COUNTER AT " + std::to_string(i+1));
}
s_running = false;
}
int main() {
std::thread th(count_thread_func);
while (s_running) {
std::this_thread::sleep_for(milliseconds(200));
output("TEST_ABC");
}
th.join();
}
相同的输出,但更清晰。此外,还有更多保证。我们可以只用 th.detach()
分离线程,或者将我们想要的任何参数传递给线程函数,而不是 void*
舞蹈。
C++17
C++14 adds some more (chrono literals), C++17 only marginally (fold
expressions used here to have natural ostream-access):
Live On Coliru
only. Note this is down to 35 LoC
回到 C++1x:ASIO
转换成 ASIO 完全不需要线程,用异步定时器代替睡眠。
因为没有线程,所以不需要任何锁定,简化了生活。
我们不需要 "running" 标志,因为如果需要,我们可以停止服务或取消计时器。
整个程序归结为:
由于我们必须在一个时间间隔执行 运行 任务,让我们将其机制放在一个简单的 class 中,这样我们就不必重复了:
// simple wrapper that makes it easier to repeat on fixed intervals
struct interval_timer {
interval_timer(boost::asio::io_context& io, Clock::duration i, Callback cb)
: interval(i), callback(cb), timer(io)
{}
void run() {
timer.expires_from_now(interval);
timer.async_wait([=](error_code ec) {
if (!ec && callback())
run();
});
}
void stop() {
timer.cancel();
}
private:
Clock::duration const interval;
Callback callback;
boost::asio::high_resolution_timer timer;
};
这对我来说似乎不言自明。整个程序现在归结为:
int main() {
boost::asio::io_context io;
interval_timer abc { io, 200ms, [] {
std::cout << "TEST_ABC" << std::endl;
return true;
} };
interval_timer counter { io, 1s, [&abc, current=0]() mutable {
std::cout << "COUNTER AT " << ++current << std::endl;
if (current < 5)
return true;
abc.stop();
return false;
} };
abc.run();
counter.run();
io.run();
}
看到了Live On Coliru.
We can simplify it a bit more if we use run_for
to limit the execution (so we don't have to deal with exiting ourselves): Live On Coliru, down to 44 LoC
#include <boost/asio.hpp>
#include <iostream>
#include <chrono>
#include <functional>
using namespace std::chrono_literals;
using Clock = std::chrono::high_resolution_clock;
using Callback = std::function<void()>;
using boost::system::error_code;
// simple wrapper that makes it easier to repeat on fixed intervals
struct interval_timer {
interval_timer(boost::asio::io_context& io, Clock::duration i, Callback cb)
: interval(i), callback(cb), timer(io)
{ run(); }
private:
void run() {
timer.expires_from_now(interval);
timer.async_wait([=](error_code ec) {
if (!ec) {
callback();
run();
}
});
}
Clock::duration const interval;
Callback callback;
boost::asio::high_resolution_timer timer;
};
int main() {
boost::asio::io_context io;
interval_timer abc { io, 200ms, [] {
std::cout << "TEST_ABC" << std::endl;
} };
interval_timer counter { io, 1s, [current=0]() mutable {
std::cout << "COUNTER AT " << ++current << std::endl;
} };
io.run_for(5s);
}
回到 C++98
没有 lambda。好的,我们可以使用 boost::bind
或者自己写一些 class。你选你的毒,我选合剂:
boost::bind
因为它是那个时代的工具(我们说的是20年前)
- 使用虚拟方法代替
std::function
用于 callback
。
- lambda 捕获已替换为显式成员变量。
这一切都变得不那么优雅了,但基本上可以识别为同一件事:
#include <boost/asio.hpp>
#include <iostream>
#include <boost/bind.hpp>
using boost::posix_time::seconds;
using boost::posix_time::millisec;
typedef boost::posix_time::microsec_clock Clock;
using boost::system::error_code;
// simple wrapper that makes it easier to repeat on fixed intervals
struct interval_timer {
interval_timer(boost::asio::io_context& io, millisec i)
: interval(i), timer(io)
{ run(); }
virtual bool callback() = 0;
void run() {
timer.expires_from_now(interval);
timer.async_wait(boost::bind(&interval_timer::on_timer, this, boost::asio::placeholders::error()));
}
void stop() {
timer.cancel();
}
private:
void on_timer(error_code ec) {
if (!ec && callback())
run();
}
millisec const interval;
boost::asio::deadline_timer timer;
};
int main() {
boost::asio::io_context io;
struct abc_timer : interval_timer {
abc_timer(boost::asio::io_context& io, millisec i) : interval_timer(io, i) {}
virtual bool callback() {
std::cout << "TEST_ABC" << std::endl;
return true;
}
} abc(io, millisec(200));
struct counter_timer : interval_timer {
counter_timer(boost::asio::io_context& io, millisec i, interval_timer& abc)
: interval_timer(io, i), abc(abc), current(0) {}
virtual bool callback() {
std::cout << "COUNTER AT " << ++current << std::endl;
if (current < 5)
return true;
abc.stop();
return false;
}
private:
interval_timer& abc;
int current;
} counter(io, millisec(1000), abc);
io.run();
}
输出还是一样可信
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 1
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 2
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 3
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 4
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 5
The same transformation as earlier with run_for
can be applied here as well, but we now have to link Boost Chrono because std::chrono
didn't exist: Live On Coliru, still 56 LoC
注意:这是针对 C++98
我正在尝试开发一个在我的主程序后台运行的简单 timer/counter。
我以前没有使用过异步定时器,我一直在尝试按照 boost 教程学习如何做到这一点,但它们似乎仍然阻碍了我的主要功能。我从 boost 网站稍微修改了 Timer.3 以进行实验。
本质上,下面的程序我想做的是:
- 运行 主要
- 执行测试运行() 计数到 5
- 同时test运行()在计数,在main中打印"TEST ABC"。
main.cpp
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
void print(const boost::system::error_code& /*e*/, boost::asio::deadline_timer* t, int* count)
{
if (*count < 5)
{
std::cout << *count << std::endl;
++(*count);
t->expires_at(t->expires_at() + boost::posix_time::seconds(1)); // every 1 second advance
t->async_wait(boost::bind(print, boost::asio::placeholders::error, t, count));
}
std::cout << " PRINT " << std::endl;
}
void testRun()
{
boost::asio::io_service io;
int count = 0;
boost::asio::deadline_timer t(io, boost::posix_time::seconds(2)); // start io object (function) after 2 seconds.
t.async_wait(boost::bind(print, boost::asio::placeholders::error, &t, &count));
io.run();
std::cout << "Final count is " << count << std::endl;
}
int main()
{
testRun();
std::cout << " TEST ABC " << std::endl;
return 0;
}
输出
0
PRINT
1
PRINT
2
PRINT
3
PRINT
4
PRINT
PRINT
Final count is 5
TEST ABC
我希望我的输出看起来像:
TEST ABC
0
PRINT
1
PRINT
2
PRINT
3
PRINT
4
PRINT
PRINT
Final count is 5
为了解构手头的任务,我将从一个简单的 C++98 实现开始。
我们会将其清理为现代 C++,然后替换为 Asio。
您会发现 Asio 不需要线程,这很好。但是我们必须回到过去,用 C++98 替换现代 C++。
最后,您将了解加入现代 C++ 的所有原因,以及如何组织代码以轻松管理复杂性。
C++98
下面是我在 c++98 中的写法:
#include <pthread.h>
#include <iostream>
#include <sstream>
#include <unistd.h>
static pthread_mutex_t s_mutex = {};
static bool s_running = true;
static bool is_running(bool newvalue) {
pthread_mutex_lock(&s_mutex);
bool snapshot = s_running;
s_running = newvalue;
pthread_mutex_unlock(&s_mutex);
return snapshot;
}
static bool is_running() {
pthread_mutex_lock(&s_mutex);
bool snapshot = s_running;
pthread_mutex_unlock(&s_mutex);
return snapshot;
}
static void output(std::string const& msg) {
pthread_mutex_lock(&s_mutex);
std::cout << msg << "\n";
pthread_mutex_unlock(&s_mutex);
}
static void* count_thread_func(void*) {
for (int i = 0; i < 5; ++i) {
::sleep(1);
std::ostringstream oss;
oss << "COUNTER AT " << (i+1);
output(oss.str());
}
is_running(false);
return NULL;
}
int main() {
pthread_t thr = {0};
pthread_create(&thr, NULL, &count_thread_func, NULL);
while (is_running()) {
::usleep(200000);
output("TEST_ABC");
}
pthread_join(thr, NULL);
}
版画
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 1
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 2
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 3
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 4
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 5
TEST_ABC
C++11
好吧,上面的内容几乎不是 C++。它实际上是 "same" 但在 C 中使用 printf
更方便。以下是 C++11 的改进方式:
- std::thread,std::atomic_bool,std::chono,std::this_thread,std::to_string,std::mutex/lock_guard,更好的初始化。
#include <thread>
#include <iostream>
#include <chrono>
#include <mutex>
#include <atomic>
using std::chrono::milliseconds;
using std::chrono::seconds;
static std::mutex s_mutex;
static std::atomic_bool s_running {true};
static void output(std::string const& msg) {
std::lock_guard<std::mutex> lk(s_mutex);
std::cout << msg << "\n";
}
static void count_thread_func() {
for (int i = 0; i < 5; ++i) {
std::this_thread::sleep_for(seconds(1));
output("COUNTER AT " + std::to_string(i+1));
}
s_running = false;
}
int main() {
std::thread th(count_thread_func);
while (s_running) {
std::this_thread::sleep_for(milliseconds(200));
output("TEST_ABC");
}
th.join();
}
相同的输出,但更清晰。此外,还有更多保证。我们可以只用 th.detach()
分离线程,或者将我们想要的任何参数传递给线程函数,而不是 void*
舞蹈。
C++17
C++14 adds some more (chrono literals), C++17 only marginally (fold expressions used here to have natural ostream-access):
Live On Coliru only. Note this is down to 35 LoC
回到 C++1x:ASIO
转换成 ASIO 完全不需要线程,用异步定时器代替睡眠。
因为没有线程,所以不需要任何锁定,简化了生活。
我们不需要 "running" 标志,因为如果需要,我们可以停止服务或取消计时器。
整个程序归结为:
由于我们必须在一个时间间隔执行 运行 任务,让我们将其机制放在一个简单的 class 中,这样我们就不必重复了:
// simple wrapper that makes it easier to repeat on fixed intervals
struct interval_timer {
interval_timer(boost::asio::io_context& io, Clock::duration i, Callback cb)
: interval(i), callback(cb), timer(io)
{}
void run() {
timer.expires_from_now(interval);
timer.async_wait([=](error_code ec) {
if (!ec && callback())
run();
});
}
void stop() {
timer.cancel();
}
private:
Clock::duration const interval;
Callback callback;
boost::asio::high_resolution_timer timer;
};
这对我来说似乎不言自明。整个程序现在归结为:
int main() {
boost::asio::io_context io;
interval_timer abc { io, 200ms, [] {
std::cout << "TEST_ABC" << std::endl;
return true;
} };
interval_timer counter { io, 1s, [&abc, current=0]() mutable {
std::cout << "COUNTER AT " << ++current << std::endl;
if (current < 5)
return true;
abc.stop();
return false;
} };
abc.run();
counter.run();
io.run();
}
看到了Live On Coliru.
We can simplify it a bit more if we use
run_for
to limit the execution (so we don't have to deal with exiting ourselves): Live On Coliru, down to 44 LoC#include <boost/asio.hpp> #include <iostream> #include <chrono> #include <functional> using namespace std::chrono_literals; using Clock = std::chrono::high_resolution_clock; using Callback = std::function<void()>; using boost::system::error_code; // simple wrapper that makes it easier to repeat on fixed intervals struct interval_timer { interval_timer(boost::asio::io_context& io, Clock::duration i, Callback cb) : interval(i), callback(cb), timer(io) { run(); } private: void run() { timer.expires_from_now(interval); timer.async_wait([=](error_code ec) { if (!ec) { callback(); run(); } }); } Clock::duration const interval; Callback callback; boost::asio::high_resolution_timer timer; }; int main() { boost::asio::io_context io; interval_timer abc { io, 200ms, [] { std::cout << "TEST_ABC" << std::endl; } }; interval_timer counter { io, 1s, [current=0]() mutable { std::cout << "COUNTER AT " << ++current << std::endl; } }; io.run_for(5s); }
回到 C++98
没有 lambda。好的,我们可以使用 boost::bind
或者自己写一些 class。你选你的毒,我选合剂:
boost::bind
因为它是那个时代的工具(我们说的是20年前)- 使用虚拟方法代替
std::function
用于callback
。 - lambda 捕获已替换为显式成员变量。
这一切都变得不那么优雅了,但基本上可以识别为同一件事:
#include <boost/asio.hpp>
#include <iostream>
#include <boost/bind.hpp>
using boost::posix_time::seconds;
using boost::posix_time::millisec;
typedef boost::posix_time::microsec_clock Clock;
using boost::system::error_code;
// simple wrapper that makes it easier to repeat on fixed intervals
struct interval_timer {
interval_timer(boost::asio::io_context& io, millisec i)
: interval(i), timer(io)
{ run(); }
virtual bool callback() = 0;
void run() {
timer.expires_from_now(interval);
timer.async_wait(boost::bind(&interval_timer::on_timer, this, boost::asio::placeholders::error()));
}
void stop() {
timer.cancel();
}
private:
void on_timer(error_code ec) {
if (!ec && callback())
run();
}
millisec const interval;
boost::asio::deadline_timer timer;
};
int main() {
boost::asio::io_context io;
struct abc_timer : interval_timer {
abc_timer(boost::asio::io_context& io, millisec i) : interval_timer(io, i) {}
virtual bool callback() {
std::cout << "TEST_ABC" << std::endl;
return true;
}
} abc(io, millisec(200));
struct counter_timer : interval_timer {
counter_timer(boost::asio::io_context& io, millisec i, interval_timer& abc)
: interval_timer(io, i), abc(abc), current(0) {}
virtual bool callback() {
std::cout << "COUNTER AT " << ++current << std::endl;
if (current < 5)
return true;
abc.stop();
return false;
}
private:
interval_timer& abc;
int current;
} counter(io, millisec(1000), abc);
io.run();
}
输出还是一样可信
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 1
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 2
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 3
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 4
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 5
The same transformation as earlier with
run_for
can be applied here as well, but we now have to link Boost Chrono becausestd::chrono
didn't exist: Live On Coliru, still 56 LoC