Чтобы разобрать задачу под рукой, я начну с простой реализации C ++ 98.
Мы очистим ее до современного C ++, а затем заменим на Asio.
Вы увидите, что Asio не требует многопоточности, что приятно. Но мы должны работать в прошлое, заменив современный C ++ на C ++ 98.
В конце вы увидите все причины присоединения к современному C ++, а также то, как организовать ваш код таким образом. что вы можете легко управлять сложностью.
C ++ 98
Вот как я написал бы это в c ++ 98:
Live On Coliru
#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 ++. На самом деле это было бы "то же самое", но более удобно в C с printf
. Вот как C ++ 11 улучшает вещи:
- std :: thread, std :: atomic_bool, std :: chono, std :: this_thread, std :: to_string, std :: mutex / lock_guard, лучшая инициализация повсюду.
Live On Coliru
#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*
dance.
C ++ 17
C ++ 14 добавляет еще немного (литералы хроно), C ++ 17 только незначительно (выражения сгиба, используемые здесь, чтобы иметь естественный ostream-доступ):
Только Live On Coliru . Обратите внимание, что до 35 Lo C
Назад к C ++ 1x: ASIO
Перевод в ASIO полностью устраняет необходимость в потоках, заменяя спящий режим асинхронными таймерами.
Поскольку нет потоков, нет необходимости в блокировке, что упрощает жизнь.
Нам не нужен флаг «работающий», потому что мы можем остановить сервис или отменить таймеры, если нам нужно.
Вся программа сводится к:
Поскольку нам придется выполнять задачи, выполняющиеся с интервалом, давайте поместим механизм для этого в простой класс, поэтому мы не будем Не надо повторять это:
// 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 .
Мы можем упростить это немного больше, если мы используем run_for
для ограничения выполнения (поэтому нам не нужно иметь дело с выходом из себя): Live On Coliru , до 44 Lo C
#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
Нет лямбды. Хорошо, мы можем использовать boost::bind
или просто написать несколько классов сами. Вы выбрали свой яд, я выбрал смесь:
boost::bind
, потому что это был инструмент той эпохи (мы говорим 20 лет go) - с использованием виртуальных метод вместо
std::function
для callback
. - Лямбда-захваты были заменены явными переменными-членами.
Все становится намного менее элегантным, но в основном распознается то же самое:
Live On Coliru
#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
Здесь можно применить то же преобразование, что и ранее с run_for
, но теперь мы должны связать Boost Chrono, потому что std::chrono
не существовало: Live On Coliru , все еще 56 Lo C