# 事件队列

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

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

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

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

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

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

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

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

下面的是一个简单样例

```cpp
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...

};
```

## 环状缓冲区

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

就像这样

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

　// Methods...


private:
　static int head_;
　static int tail_;

　// Array...
};
```

加入队列

```cpp
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;
}
```

更新方法

```cpp
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;
}
```

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

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

## 入队的是什么

* 如果队列中是事件，一个“事件”或“通知”描述已经发生的事情，比如“怪物死亡”。你将它入队，所以其他对象可以响应事件，有几分像一个异步的观察者模式。
* 如果队列中是消息，一个“消息”或“请求”描述一种“我们期望”发生在“将来”的行为，类似于“播放音乐”。你可以认为这是的一个异步API服务。

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