Подтвердить что ты не робот

Реализация класса С++ "Timer"

Я разработал класс Timer, который отправляет (используя шаблон Observer) событие каждое n n-секунд. Конечно, он создает новый поток, чтобы не блокировать поток, из которого он был вызван.

Тогда я подумал... хм... скажите, что 100 клиентов подключились к моей серверной программе, я создаю 3 таймера для каждого из них, поэтому я запускаю 300 потоков. Разве это не так много? Это ok, что я запускаю 300 потоков?

Затем я был сказал, что в AS3 Timer работает в основном потоке. И я подумал: КАК??? Как я могу реализовать таймер, работающий в основном потоке, а не его блокировку? Возможно ли это на С++?

4b9b3361

Ответ 1

Возможное решение состоит в том, чтобы просто использовать один поток для всех таймеров и иметь очередь, упорядоченную таймаутом. Проблема заключается в том, что, когда таймер истекает, и вы вызываете функцию обратного вызова, он запускается в контексте потока глобального таймера, а не отдельно. Это можно, конечно, решить, создав новый поток только для события, затем соединяется напрямую или пулом потоков для обработки событий, поэтому основной поток таймера не будет "засорен".

Ответ 2

Вы можете создать один поток таймера, и для каждого клиента, который "регистрируется", создайте запись в дереве. Ключ будет временем ожидания клиента, и значение будет ссылкой на клиента. Это приведет к заказу клиентов по истечении времени ожидания.

Затем для таймера установите циклический таймер, скажем каждые 100 миллисекунд (настройтесь соответственно). Когда таймер истекает, повторите удаление дерева и отправку каждого клиента, который истекло. Итерация должна прекратиться, когда вы достигнете таймаута клиента, который еще не завершился.

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

Ответ 3

Теперь это вопрос дизайна, поэтому у всех разные мнения, и это также зависит от ваших требований, но IMO, таймер не должен сам решать политику потоковой передачи - клиент должен это сделать.

Я не уверен, какое поведение вы ожидаете, но если вы запускаете 300 событий по таймеру в одном потоке и по одному блоку обработчика событий, другие обработчики событий никогда не будут запущены.

Одна из возможностей - создать таймер в потоке, но реализовать его так, чтобы обработчики событий выполнялись на других потоках через пул потоков. Конечно, все равно можно сломать вещи, потому что, если у вас много длинных обработчиков, пул потоков может исчерпаться.

Я настоятельно рекомендую не использовать явный новый поток для каждого обработчика, поскольку переключение контекста, вероятно, приведет к снижению производительности. Пул потоков намного лучше балансирует это.

Ответ 4

Что касается реализации таймера в основном потоке, то должен быть некоторый механизм, который периодически вызывается из кода пользователя (например, во время опроса событий), который также обрабатывает таймеры. Конечно, такой подход, скорее всего, неточный, поскольку он может выполнять только таймеры, когда это разрешает код пользователя в основном потоке.

Также он будет блокировать основной поток, пока код обратного вызова будет выполнен, конечно.

Ответ 5

В вашем первом вопросе уже достаточно ответов: пул потоков (набор из 5 или 10 потоков), которые обрабатывают события таймера, является обычным способом сделать это и хорошим компромиссом между одним потоком для каждого события и одним потоком для всех событий.

Что касается вашего второго вопроса: использование обычного программирования означает, что вы не можете выполнить обработчик события таймера в основном потоке. Если бы вы могли "заблокировать" основной поток, но это невозможно без согласия и поддержки от кода, выполняемого в основном потоке.

Основной поток должен время от времени останавливаться и проверять, есть ли событие из таймера, принимать параметры из таймера в форме объекта и обрабатывать событие. Существует много способов разработки этого принципа, но это общие способы, как вы это делаете.

В системах Unix вы также можете использовать сигналы, но я считаю, что это не очень хорошая идея.

Ответ 6

Ваш сервер может запускать один поток таймера для всех таймеров. Этот timer wheel создает события, когда таймеры клиентов регистрируются на колесах таймера серверов. Когда зарегистрированный таймер истечет, событие устанавливается колесом таймера. Клиенты получают дескриптор события, созданного во время регистрации таймера. Клиенты могут дождаться, пока события, сигнализирующие о выкупе зарегистрированного таймера. Таким образом, создание потоков зависит от клиентов.

Ответ 7

Поскольку вы разрабатываете на С++, вы можете использовать Boost ASIO для этого. Я также разработал класс Timer, основанный на них, и он работает красиво и без каких-либо потоков - он использует асинхронные вызовы в ОС, поэтому вам просто нужно определить обратный вызов, который будет вызываться, когда таймер истекает, а затем вызвать таймер async_wait, которая не блокирует. Когда вы объявляете свой объект таймера, вам просто нужно передать ему объект io_service, который является интерфейсом ASIO для O.S. Этот объект отвечает за обслуживание ваших асинхронных запросов и обратных вызовов, поэтому для этого вы можете вызвать его метод блокировки выполнить. В моем случае я не мог блокировать основной поток, поэтому у меня был только один поток, в котором этот уникальный вызов блокировался.

Здесь вы можете найти примеры использования асинхронного таймера Boost ASIO:

http://www.boost.org/doc/libs/1_52_0/doc/html/boost_asio/tutorial/tuttimer2.html

Мой класс AbstractAsioTimer был разработан для подкласса, поэтому метод onTimerTick был бы специфичен для концов производного класса. Хотя ваши потребности могут быть немного разными, это может быть хорошей отправной точкой:

abstractasiotimer.hpp:

#ifndef _ABSTRACTASIOTIMER_HPP_
#define _ABSTRACTASIOTIMER_HPP_

#include <boost/asio.hpp>

/**
 * Encapsulates a POSIX timer with microsecond resolution
 */
class AbstractAsioTimer
{
  public:
    /**
     * Instantiates timer with the desired period
     * @param io ASIO interface object to the SO
     * @param timeout time in microseconds for the timer handler to be executed
     */
    AbstractAsioTimer(boost::asio::io_service& io, unsigned int timeout);

    /**
     * Destructor
     */
    virtual ~AbstractAsioTimer();

    /**
     * Starts timer operation
     */
    void timerStart();

    /**
     * Stops timer operation
     */
    void timerStop();

    /**
     * Returns timer operation state
     */
    bool isRunning() const;

    /**
     * Returns a reference to the underlying io_service
     */
    boost::asio::io_service& get_io_service();

  protected:
    /**
     * Timer handler to execute user specific code
     * @note must be reimplemented in derived classes
     */
    virtual void onTimerTick() = 0;

  private:
    /**
     * Callback to be executed on timer expiration. It is responsible
     * for calling the 'onTimerTick' method and restart the timer if 
     * it remains active
     */
    void timerExpired(const boost::system::error_code& error);

    boost::asio::deadline_timer timer; /**< ASIO timer object */
    unsigned int timeout; /**< Timer period in microseconds */
    bool running; /**< Flag to indicate whether the timer is active */
};
#endif

abstractasiotimer.cpp:

#include <iostream>
#include <boost/bind.hpp>
#include <boost/concept_check.hpp>
#include "abstractasiotimer.hpp"

using namespace boost::asio;

AbstractAsioTimer::AbstractAsioTimer(boost::asio::io_service& io, 
                                     unsigned int timeout):
                                     timer(io), timeout(timeout),
                                     running(false)
{

}

AbstractAsioTimer::~AbstractAsioTimer()
{
  running = false;
  timer.cancel();
}

void AbstractAsioTimer::timerExpired(const boost::system::error_code& error) {

  if (!error) {
    onTimerTick();
    //Restart timer
    timerStart();
  }
  else {
    running = false;
    std::cerr << "Timer stopped: " << error.message() << std::endl;
  }
}

void AbstractAsioTimer::timerStart()
{
  timer.expires_from_now(boost::posix_time::microseconds(timeout));
  timer.async_wait(boost::bind(&AbstractAsioTimer::timerExpired,
                   this, placeholders::error));
  running = true;
}

void AbstractAsioTimer::timerStop() {
  running = false;
  timer.cancel();
}

bool AbstractAsioTimer::isRunning() const {
  return running;
}

io_service& AbstractAsioTimer::get_io_service()
{
  return timer.get_io_service();
}