C ++ повышает асинхронный таймер для запуска параллельно с программой - PullRequest
1 голос
/ 21 апреля 2020

Примечание: это для C ++ 98

Я пытаюсь разработать простой таймер / счетчик, который работает в фоновом режиме моей основной программы.

Я не использовал асинхронные таймеры раньше, и я пытался следовать учебным пособиям по повышению, как это сделать, но они все еще, кажется, блокируют мою основную функцию. Я немного изменил Timer.3 с веб-сайта буста, чтобы поэкспериментировать.

По сути, с программой, которую я хочу сделать ниже:

  1. Выполнить main
  2. Выполнить testRun (), который считается до 5
  3. В то же время, когда testRun () считает, выведите «TEST AB C» в main.

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;
}

output

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

1 Ответ

2 голосов
/ 22 апреля 2020

Чтобы разобрать задачу под рукой, я начну с простой реализации 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

...