这个范例比较简单,通过 Actor 输出了 Hello Theron。需要额外说明的一点是消息在 Actor 之间发送时会被拷贝,接收到消息的 Actor 只是引用到被发送消息的一份拷贝,这么做的目的在于避免引入共享内存、同步等问题。

  Theron 的消息处理

  前面谈到过,每个 Actor 都工作在一个属于自己的“线程”上,我们通过一个例子来认识这一点,我们修改上面例子中的 Actor:: Handler 成员方法:

inline void Handler(const StringMessage& message, const Theron::Address from)
{
    while (true)
    {
        printf("%s --- %d ", message.m_string, GetAddress().AsInteger());
#ifdef _MSC_VER
        Sleep(1000);
#else
        sleep(1);
#endif
    }
}

  此 Handler 会不断的打印 message 并且带上当前 Actor 的地址信息。在 main 函数中,我们构建两个 Actor 实例并通过消息唤醒它们,再观察输出结果:

Hello Theron! --- 1
Hello Theron! --- 2
Hello Theron! --- 2
Hello Theron! --- 1
Hello Theron! --- 2
Hello Theron! --- 1
Hello Theron! --- 2
Hello Theron! --- 1
......

  这和我们预期的一样,两个 Actor 实例在不同的线程下工作。实际上,Framework 创建的时候会创建系统级的线程,默认情况下会创建两个(可以通过 Theron::Framework 构造函数的参数决定创建线程的数量),它们构成一个线程池,我们可以根据实际的 CPU 核心数来决定创建线程的数量,以确保 CPU 被充分利用。线程池的线程是以何种方式进行调度的?如下图:

  接收到消息的 Actor 会被放置于一个线程安全的 Work 队列中,此队列中的 Actor 会被唤醒的工作线程取出,并进行消息的处理。这个过程中有两个需要注意的地方:

  1、对于某个 Actor 我们可以为某个消息类型注册多个消息处理函数,那么此消息类型对应的多个消息处理函数会按照注册的顺序被串行执行下去

  2、线程按顺序处理 Actor 收到的消息,一个消息未处理完成不会处理消息队列中的下一个消息 我们可以想象,如果存在三个 Actor,其中两个 Actor 的消息处理函数中存在死循环(例如上例中的 while(true)),那么它们一旦执行会霸占两条线程,若线程池中没有多余线程,那么另一个 Actor 将被“饿死”(永远得不到执行)。我们可以在设计上避免这种 Actor 的出现,当然也可以适当的调整线程池的大小来解决此问题。Theron 中,线程池中线程的数量是可以动态控制的,线程利用率也可以测量。但是务必注意的是,过多的线程必然导致过大的线程上下文切换开销。