如何实现令牌队列(token queue)
令牌环在计算机网络中有广泛的应用。在程序中有时也需要应用到令牌技术。 这里介绍一下令牌队列的实现。
一个令牌队列是一系列需要执行的调用的集合,它的作用有两点:
1. 保持调用的顺序
2. 调用间互斥执行
这两点都是通过令牌来实现的。规则是:只有获得令牌的调用才能执行,执行完毕后将令牌归还。
令牌队列里最简单的情况是:只有一个令牌,任何时候只有一个调用处于执行状态。
以下是典型的使用场景:
1. 客户代码请求获得令牌以执行一个调用
2. 令牌队列接收该请求,将该请求放入待执行队列
4. 调用执行完毕,将令牌归还,回到步骤3
简单的代码实现:
class TokenQueue {
public:
void requestToken(CallBack cb); // 请求令牌,cb是需要执行的函数调用, 对应步骤1,2
void acquireToken(); // 获得token,增加m_activeCalls计数, 包含在步骤3里
void returnToken(); // 返还token, 减少m_activeCalls计数 对应步骤4
private:
void process(); // 做实际的调用,对应步骤3
private:
std::queue<CallBack> m_callQueue;
int m_activeCalls;
};
伪代码实现:
{
m_callQueue.push_back(cb);
process();
}
void acquireToken()
{
++m_activeCalls;
}
void returnToken()
{
--m_activeCalls;
process();
}
void process()
{
if (m_activeCalls == 0 && !m_callQueue.empty()) {
acquireToken();
CallBack cb = m_callQueue.front();
returnToken();
}
}
看上去差不多了,但是这里有个致命问题。
requestToken()是阻塞式的,假设这样一种场景:
A 调用requestToken(), 获得token,开始执行,需要执行十分钟
而在这十分钟之内发生了以下事情,
B 调用requestToken(), 由于token被A获得,直接返回
这样, A调用完毕,返回token, 进入process(), 发现callQueue已经很大,于是不停的assignToekn, execute, returnToken, process.
这样,A原本只用执行10分钟的call ,却需要额外处理这期间所有其他call。这显然是不对的。
有两个办法解决这个问题:
1. returnToken() 里不做process() 而是在requestToken()里等待token。
用pthread_cond_t的pthread_cond_wait, pthread_cond_signal可以实现。但是由于需要保证执行顺序,需要保存一个condition queue.
2. 只需要在process()里把同步调用改成异步调用:
假设存在一个asyncCallManager, process()里的代码变成这样:
{
if (m_activeCalls == 0 && !m_callQueue.empty()) {
acquireToken();
CallBack cb = m_callQueue.front();
m_callQueue.pop_front()();
AsyncCallManager::schedule(boost::bind(callWrapper, this, cb));
}
}
void callWarpper(CallBack cb) {
cb();
returnToken();
}
至此,大框架构造完毕。还有一些需要注意的问题。
1. 这个TokenQueue不是线程安全的。这个简单,用pthread_mutex_t保护一下就可以了。
2. asyncCallManager需要在一个独立的线程里执行。
3. 涉及CallBack的异步程序最头疼的问题就是如何在调用callback时保证句柄的有效。
posted on 2011-08-30 16:06 freestyleking 阅读(806) 评论(0) 编辑 收藏 举报