我设计了一个类,它每n 秒Timer
分派(使用观察者模式)一个事件。n
当然,它会创建一个新线程,以免阻塞调用它的线程。
然后我想 - 嗯......假设有 100 个客户端连接到我的服务器程序,我为每个客户端创建 3 个计时器,所以我运行 300 个线程。不是很多吗?是不是ok
,我运行 300 个线程?
然后我被告知在 AS3 中Timer
运行在主线程中。我想知道:怎么???如何实现在主线程中运行的计时器而不阻塞它?在 C++ 中可能吗?
一个可能的解决方案是只为所有计时器使用一个线程,并有一个按超时排序的队列。这样做的问题是,当计时器到期并且您调用回调函数时,它将在全局计时器线程的上下文中运行,而不是单独运行。这当然可以通过为事件生成一个新线程来解决,即然后直接加入,或者通过线程池来处理事件,因此主计时器线程不会被“阻塞”。
您可以创建一个计时器线程,并为每个“注册”的客户端在树中创建一个条目。键是客户端超时,值是对客户端的引用。这将按超时对客户端进行排序。
然后对于计时器,设置一个循环计时器,例如每 100 毫秒(相应调整)。当计时器到期时,迭代树删除并调度每个超时的客户端。当您达到尚未超时的客户端超时时,迭代应该停止。
对这种方法更准确的改进是当计时器到期并且客户端被分派时,计算下一个客户端的超时并相应地设置计时器。这仅取决于解决方案需要有多准确。
现在这是一个设计问题,所以每个人都有不同的意见,这也取决于您的要求,但是 IMO,计时器不应该自己决定线程策略 - 客户端应该这样做。
我不确定您期望什么行为,但是如果您在同一线程上的计时器上运行 300 个事件,并且由于某种原因一个事件处理程序阻塞,则永远不会触发其他事件处理程序。
一种可能性是在线程上创建一个计时器,但以事件处理程序通过线程池在其他线程上执行的方式实现它。当然,仍然有可能破坏事物,因为如果您有许多长时间运行的处理程序,线程池可能会耗尽。
我强烈建议不要为每个处理程序使用显式的新线程,因为上下文切换可能会降低性能。线程池在平衡这一点方面要好得多。
就在主线程中实现定时器而言,必须有一些机制从用户代码中定期调用(例如,在事件轮询期间),它也处理定时器。当然,这种方法很可能是不准确的,因为它只能在主线程中的用户代码允许的情况下执行定时器。
当然,它还会在执行回调代码时阻塞主线程。
您的第一个问题已经有足够的答案:处理计时器事件的线程池(一组说 5 或 10 个线程)是执行此操作的常用方法,并且在每个事件的一个线程和所有事件的一个线程之间进行了很好的折衷.
关于您的第二个问题:使用常规编程意味着您无法在主线程中执行计时器事件处理程序。如果可以的话,它会“阻塞”主线程,但如果没有在主线程中执行的代码的同意和支持,这是不可能的。
主线程会时不时停下来检查定时器是否有事件,以对象的形式从定时器中获取参数,然后处理事件。有很多方法可以设计这个原则,但这是你如何做到这一点的一般方法。
在 Unix 系统上,您可能还会考虑使用信号,但我认为这不是一个好主意。
您的服务器可能为所有计时器运行一个计时器线程。timer wheel
当客户端计时器注册到服务器计时器轮时,这会创建事件。当注册的定时器超时时,事件由定时器轮设置。客户端获取在注册计时器时创建的事件的句柄。客户端可以等待通知注册定时器超时的事件。这样线程创建取决于客户端。
由于您使用 C++ 进行设计,因此您可以使用Boost ASIO计时器。我还设计了一个基于它们的 Timer 类,它工作得很好,没有任何线程 - 它使用对操作系统的异步调用,所以基本上你只需要定义一个回调,它将在定时器到期时调用,然后调用定时器的async_wait函数,它是非阻塞的。当您声明您的计时器对象时,您只需向它传递一个io_service对象,该对象是 OS 的 ASIO 接口。该对象负责为您的异步请求和回调提供服务,因此您可以调用其阻塞方法run。在我的情况下,我不能让主线程阻塞,所以我只有一个线程,这个独特的调用被阻塞了。
在这里您可以找到有关如何使用 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();
}