如何实现令牌队列(token queue)

令牌环在计算机网络中有广泛的应用。在程序中有时也需要应用到令牌技术。 这里介绍一下令牌队列的实现。

一个令牌队列是一系列需要执行的调用的集合,它的作用有两点:

1. 保持调用的顺序

2. 调用间互斥执行

这两点都是通过令牌来实现的。规则是:只有获得令牌的调用才能执行,执行完毕后将令牌归还。

令牌队列里最简单的情况是:只有一个令牌,任何时候只有一个调用处于执行状态。

 以下是典型的使用场景:

1. 客户代码请求获得令牌以执行一个调用

2. 令牌队列接收该请求,将该请求放入待执行队列

 

3. 当令牌不被其他调用占用时,令牌队列选取一个调用,赋予令牌并执行. 若令牌被占用,则退出

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;

};

 

 伪代码实现:

 

 

void requestToken(CallBack cb)
{

     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();
         m_callQueue.pop_front()();
         cb();

         returnToken();
    }

}


 看上去差不多了,但是这里有个致命问题。

requestToken()是阻塞式的,假设这样一种场景:

A 调用requestToken(), 获得token,开始执行,需要执行十分钟

而在这十分钟之内发生了以下事情,

B 调用requestToken(), 由于token被A获得,直接返回

C 调用requestToken(), 由于token被A获得,直接返回
D 调用requestToken(), 由于token被A获得,直接返回
E 调用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()里的代码变成这样:

 

 void 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  阅读(794)  评论(0编辑  收藏  举报

导航