事件队列

“对消息或事件的发送与受理进行时间上的解耦。”

我们经常听到 消息队列、事件循环、消息泵。

事件队列是一个按照先进先出顺序存储一系列通知或请求的队列。发出通知时系统会将该请求置入队列并随即返回,请求处理器随后从事件队列中获取并处理这些请求。请求可由处理器直接处理或转交给对其感兴趣的模块。这一模式对消息的发送者与受理者进行了解耦,使消息的处理变得动态且非实时。

A想让B做某些事,A将事件塞入事件队列,然后事件读时处理时,执行事件。这样A就不会卡住了。

但是,如果A发事件,然后目标会同步直接处理,可能会造成循环问题,例如

  1. A发送一个事件
  2. B接收它,之后发送一个响应事件
  3. 这个响应事件恰好是A关心的,所以接受它,作为返回A也会发送一个响应事件
  4. 回到2

但将事件缓存到某个地方,然后下一帧去读,然后处理,能解决这种问题。

或者,一个常用的规避法则是避免在处理事件端代码中发送事件。

下面的是一个简单样例

class Audio
{
public:
 static void init() { numPending_ = 0; }

 // Other stuff...

private:
 static const int MAX_PENDING = 16;

 static PlayMessage pending_[MAX_PENDING];
 static int numPending_;
};

void Audio::playSound(SoundId id, int volume)
{
 assert(numPending_ < MAX_PENDING);

 pending_[numPending_].id = id;
 pending_[numPending_].volume = volume;
 numPending_++;
}

class Audio
{
public:
  // 在更新方法中处理事件
 static void update()
 {
  for (int i = 0; i < numPending_; i++)
  {
   ResourceId resource = loadSound(
      pending_[i].id);
   int channel = findOpenChannel();
   if (channel == -1) return;
   startSound(resource, channel,
     pending_[i].volume);
  }

  numPending_ = 0;
 }

 // Other stuff...

};

环状缓冲区

环状缓冲区保有数组所有的有点,同时允许我们从队列的前端持续地移除元素。

就像这样

class Audio
{
public:
 static void init()
 {
  head_ = 0;
  tail_ = 0;
 }

 // Methods...


private:
 static int head_;
 static int tail_;

 // Array...
};

加入队列

void Audio::playSound(SoundId id, int volume)
{
 assert((tail_ + 1) % MAX_PENDING != head_);

 // Add to the end of the list.
 pending_[tail_].id = id;
 pending_[tail_].volume = volume;
 tail_ = (tail_ + 1) % MAX_PENDING;
}

更新方法

void Audio::update()
{
 //If there are no pending requests, do nothing.

 if (head_ == tail_) return;

 ResourceId resource = loadSound(
  pending_[head_].id);

 int channel = findOpenChannel();
 if (channel == -1) return;
 startSound(resource, channel,
  pending_[head_].volume);

 head_ = (head_ + 1) % MAX_PENDING;
}

如果最大容量会有问题,你可以使用可增长的数组。当队列满了以后,分配一个新的数组,大小是当前数组的二倍(或其他的倍数),并把原数组中的项拷贝过去。

即使在数组增长的时候拷贝,入列一个元素仍然有常量级的复杂度。

入队的是什么

Go编程语言内置的“通道”类型,本质就是一个事件队列或消息队列。