2012-09-13 11 views
13

He diseñado una clase Timer, que distribuye (utilizando un patrón Observer) un evento cada n n segundos. Por supuesto, crea un nuevo hilo para no bloquear el hilo desde el que se invocó.С ++ Implementación de la clase `Timer`

Entonces he pensado - mmm ... digamos que 100 clientes se conectan a mi programa de servidor, creo 3 temporizadores para cada uno de ellos, así que ejecuto 300 hilos. ¿No es mucho? ¿Es ok, que ejecuto 300 hilos?

Luego estaba told que en AS3 Timer se ejecuta en el hilo principal. Y me preguntaba: ¿CÓMO? ¿Cómo puedo implementar un temporizador ejecutándose en el hilo principal y no bloquearlo? ¿Es posible en C++?

+0

más (o por lo al menos muchas) implementaciones de temporizador realmente están bloqueando. No hay nada intrínsecamente mal con eso. Sin embargo, obviamente depende del propósito. –

Respuesta

7

Una posible solución es usar solo un hilo para todos los temporizadores y tener una cola ordenada por el tiempo de espera. El problema con esto es que cuando expira un temporizador y se llama a la función de devolución de llamada, se ejecutará en el contexto del hilo del temporizador global y no por separado. Esto, por supuesto, se puede resolver al generar un nuevo hilo solo para el evento, que es luego se unió directamente, o al tener un grupo de subprocesos para manejar los eventos, por lo que el subproceso principal del temporizador no se "obstruirá".

2

Puede crear una única secuencia de temporizador, y para cada cliente que "registra", cree una entrada en un árbol. La clave sería el tiempo de espera del cliente y el valor sería una referencia para el cliente. Esto ordenaría a los clientes por su tiempo de espera.

Luego, para el temporizador, configure un temporizador cíclico, digamos cada 100 milisegundos (sintonice en consecuencia). Cuando el temporizador expira, itere el árbol eliminando y despachando a cada cliente que ha agotado el tiempo de espera. La iteración debe detenerse cuando alcanza un tiempo de espera del cliente que aún no ha excedido el tiempo de espera.

Una mejora más precisa de este enfoque sería cuando expire el temporizador, y se despachen los clientes, calcule el tiempo de espera del próximo cliente y configure el temporizador en consecuencia. Solo depende de qué tan precisa sea la solución.

1

Ahora esta es una pregunta de diseño, por lo que cada persona tiene opiniones diferentes y también depende de sus requisitos, pero IMO, el temporizador no debe decidir la política de enhebrado, el cliente debería hacerlo.

No estoy seguro de qué comportamiento espera, pero si ejecuta 300 eventos en un temporizador en el mismo subproceso y un controlador de eventos bloquea por algún motivo, nunca se desencadenarán otros controladores de eventos.

Una posibilidad es crear un temporizador en un subproceso, pero impleméntelo de forma que los controladores de eventos se ejecuten en otros subprocesos a través del grupo de subprocesos. Por supuesto, todavía es posible romper cosas, porque si tienes muchos manejadores de larga ejecución, el grupo de subprocesos podría agotarse.

Recomiendo encarecidamente no utilizar un nuevo hilo explícito para cada controlador, ya que el cambio de contexto probablemente mataría el rendimiento. El grupo de subprocesos es mucho mejor para equilibrar esto.

1

En cuanto a la implementación de un temporizador en el hilo principal, tiene que haber algún mecanismo que se llame periódicamente desde el código de usuario (por ejemplo, durante el sondeo de eventos) que también maneja los temporizadores. Por supuesto, tal enfoque es probablemente inexacto, ya que solo puede ejecutar temporizadores cuando el código de usuario en el hilo principal lo permite.

También bloqueará el hilo principal mientras se ejecuta el código de devolución de llamada, por supuesto.

1

Su primera pregunta ya tiene suficientes respuestas: Un grupo de subprocesos (un conjunto de 5 o 10 subprocesos) que maneja los eventos del temporizador es la manera habitual de hacerlo y un buen compromiso entre un hilo para cada evento y uno hilo para todos los eventos.

En relación con su segunda pregunta: el uso de programación regular significa que no puede ejecutar el controlador de eventos del temporizador en el hilo principal. Si pudiera, "bloquearía" el hilo principal, pero eso no es posible sin el consentimiento y el respaldo del código que se ejecuta en el hilo principal.

El hilo principal debería detenerse de vez en cuando y comprobar si hay un evento del temporizador, tomar los parámetros del temporizador en forma de un objeto y manejar el evento. Hay muchas maneras de diseñar este principio, pero esa es la forma general en que lo hace.

En sistemas Unix, también podría pensar en usar señales, pero creo que no es una buena idea.

1

Su servidor puede ejecutar una secuencia de temporizador para todos los temporizadores. Este timer wheel crea eventos cuando los temporizadores de los clientes se registran en la rueda del temporizador de los servidores. Cuando el temporizador registrado expira, el evento es configurado por la rueda del temporizador. Los clientes obtienen el identificador del evento creado en el momento en que se registró el temporizador. Los clientes pueden esperar los eventos que señalan el tiempo de espera registrado del temporizador. De esta forma, la creación del hilo depende de los clientes.

0

Como está diseñando en C++, puede usar los temporizadores Boost ASIO para eso. También diseñé una clase Timer basada en ellos y funciona muy bien y sin ningún tipo de subprocesos: utiliza llamadas asíncronas al sistema operativo, por lo que básicamente solo tiene que definir una devolución de llamada que se ejecutará cuando expire el temporizador y luego llame al del temporizador Función async_wait, que no es bloqueante. Cuando declare su objeto de temporizador solo tiene que pasarle un objeto io_service que es la interfaz ASIO para el O.S. Este objeto es responsable de dar servicio a sus solicitudes asincrónicas y devoluciones de llamada, por lo que puede llamar a su método de bloqueo ejecutar. En mi caso, no podía tener el bloqueo del hilo principal, así que solo tenía un hilo en el que bloqueaba esta llamada única.

Aquí se pueden encontrar ejemplos de cómo utilizar el refuerzo ASIO temporizador asíncrono:

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

Mi AbstractAsioTimer clase fue diseñado para ser una subclase lo que el método onTimerTick sería específica a la deriva la clase termina A pesar de que sus necesidades podrían ser un poco diferente, podría ser un buen punto de partida:

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