详解Theron通过Actor模型解决C++并发编程的一种思维
现今,单台机器拥有多个独立的计算单元已经太常见了,这点在服务器的处理器上表现尤为明显,据AMD的一张2012-2013服务器路线图显示,服务器处理器的核心数将在2013年达到20颗之多,合理的利用CPU资源已是一个不得不考虑的问题。
不少C++程序员依然使用着多线程模型,但是对多线程的掌控并不是一件容易的事情,开发中容易出错、难以调试,有些开发者为了避免多线程带来的复杂度而弃用多线程,有些开发者则另投其他语言阵营,例如:Erlang,其实我们还有其他的选择,Theron就是其中之一。
1、什么是Theron?
Theron是一个用于并发编程的C++库,通过Theron我们可以避免多线程开发中各种痛处,例如:共享内存、线程同步,Theron通过Actor模型向我们展示了另一种思维。
2、什么是Actor模型?
Erlang因为其优秀的并发特性而被大家所关注,而其并发特性的关键之一就是在于其采用了Actor模型,与Actor模型相对应的模型则是我们在面向对象编程中使用的Object模型,Object模型中宣扬,一切皆为Object(对象),而Actor模型则认为一切皆为Actor。
Actor模型中,Actor之间通过消息相互通信,这是其和Object模型的一个显著的区别,换而言之Actor模型使用消息传递机制来代替了Object模型中的成员方法调用。
在马海祥看来,这样做意义重大,因为相对于成员方法的调用来说,消息的发送是非阻塞的,它无需等待被调用方法执行完成就可以返回,下图显示了此种区别:
A::a()调用了objB.b(),此时A::a()必须等待B::b()的返回才能继续执行,在Actor模型中,对应的做法是Actor A向Actor B发送消息并立即返回,这时候Actor A可以继续执行下去,与此同时Actor B收到消息被唤醒并和Actor A并行执行下去。
Theron中的每个Actor都会绑定一个唯一的地址,通过Actor的地址就可以向其发送消息了,每个Actor都有一个消息队列。
从编码者的角度看来,每实例化一个Actor都创建了一个和Actor相关的“线程”(非系统级的线程),每个Actor总是被单线程的执行。
总的来说,Theron的并发特性的关键就在于:每个Actor在属于自己的单个“线程”中执行,而多个Actor并发执行。
3、Hello Theron
在谈及更多内容之前,我们先来看看Theron的一个简单的范例,借以获得一个最直观的印象,Theron提供了makefile便于gcc用户编译,同时其也为Windows用户提供了Visual Studio solution文件Theron.sln用于构建Theron。
编译Theron很容易,不会有太多的障碍,需要注意的是构建Theron需要指定依赖的线程库,Theron支持三种线程库:std::thread(C++11 标准线程库)、Boost.Thread和Windows threads。
使用makefile构建时,通过threads参数指定使用的线程库,使用Visual Studio构建时,通过选择适当的Solution configuration来指定使用的线程库,下面我们来看一个最简单的范例:
#include <stdio.h>
#include <Theron/Framework.h>
#include <Theron/Actor.h>
// 定义一个消息类型
// 在 Theron 中,任何类型都可以作为一个消息类型
// 唯一的一个约束是消息类型的变量能够被拷贝的
// 消息按值发送(而非发送它们的地址)
struct StringMessage
{
char m_string[64];
};
// 用户定义的 Actor 总需要继承于 Theron::Actor
// 每个 Actor 和应用程序的其他部分通信的唯一途径就是通过消息
class Actor : public Theron::Actor
{
public:
inline Actor()
{
// 注册消息的处理函数
RegisterHandler(this, &Actor::Handler);
}
private:
// 消息处理函数的第一个参数指定了处理的消息的类型
inline void Handler(const StringMessage& message, const Theron::Address from)
{
printf("%sn", message.m_string);
if (!Send(message, from))
printf("Failed to send message to address %dn", from.AsInteger());
}
};
int main()
{
// Framework 对象用于管理 Actors
Theron::Framework framework;
// 通过 Framework 构建一个 Actor 实例并持有其引用
// Actor 的引用类似于 Java、C# 等语言中的引用的概念
// Theron::ActorRef 采用引用计数的方式实现,类似于 boost::shared_ptr
Theron::ActorRef simpleActor(framework.CreateActor<Actor>());
// 创建一个Receiver用于接收Actor发送的消息
// 用于在非Actor代码中(例如main函数中)与Actor通信
Theron::Receiver receiver;
// 构建消息
StringMessage message;
strcpy(message.m_string, "Hello Theron!");
// 通过 Actor 的地址,我们就可以向 Actor 发送消息了
if (!framework.Send(message, receiver.GetAddress(), simpleActor.GetAddress()))
printf("Failed to send message!n");
// 等到 Actor 发送消息,避免被关闭主线程
receiver.Wait();
return 0;
}
这个范例比较简单,通过Actor输出了Hello Theron,需要额外说明的一点是消息在Actor之间发送时会被拷贝,接收到消息的Actor只是引用到被发送消息的一份拷贝,这么做的目的在于避免引入共享内存、同步等问题。
Theron的消息处理前面谈到过,每个Actor都工作在一个属于自己的“线程”上,我们通过一个例子来认识这一点,我们修改上面例子中的Actor:: Handler成员方法:
inline void Handler(const StringMessage& message, const Theron::Address from)
{
while (true)
{
printf("%s --- %dn", 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中,线程池中线程的数量是可以动态控制的,线程利用率也可以测量,但是务必注意的是,过多的线程必然导致过大的线程上下文切换开销。
4、一个详细的例子
我们再来看一个详细的例子,借此了解Theron带来的便利,生产者消费者的问题是一个经典的线程同步问题,我们来看看Theron如何解决这个问题:
#include <stdio.h>
#include <Theron/Framework.h>
#include <Theron/Actor.h>
const int PRODUCE_NUM = 5;
class Producer : public Theron::Actor
{
public:
inline Producer(): m_item(0)
{
RegisterHandler(this, &Producer::Produce);
}
private:
// 生产者生产物品
inline void Produce(const int& /* message */, const Theron::Address from)
{
int count(PRODUCE_NUM);
while (count--)
{
// 模拟一个生产的时间
#ifdef _MSC_VER
Sleep(1000);
#else
sleep(1);
#endif
printf("Produce item %dn", m_item);
if (!Send(m_item, from))
printf("Failed to send message!n");
++m_item;
}
}
// 当前生产的物品编号
int m_item;
};
class Consumer : public Theron::Actor
{
public:
inline Consumer(): m_consumeNum(PRODUCE_NUM)
{
RegisterHandler(this, &Consumer::Consume);
}
private:
inline void Consume(const int& item, const Theron::Address from)
{
// 模拟一个消费的时间
#ifdef _MSC_VER
Sleep(2000);
#else
sleep(2);
#endif
printf("Consume item %dn", item);
--m_consumeNum;
// 没有物品可以消费请求生产者进行生产
if (m_consumeNum == 0)
{
if (!Send(0, from))
printf("Failed to send message!n");
m_consumeNum = PRODUCE_NUM;
}
}
int m_consumeNum;
};
int main()
{
Theron::Framework framework;
Theron::ActorRef producer(framework.CreateActor<Producer>());
Theron::ActorRef consumer(framework.CreateActor<Consumer>());
if (!framework.Send(0, consumer.GetAddress(), producer.GetAddress()))
printf("Failed to send message!n");
// 这里使用 Sleep 来避免主线程结束
// 这样做只是为了简单而并不特别合理
// 在实际的编写中,我们应该使用Receiver
#ifdef _MSC_VER
Sleep(100000);
#else
sleep(100);
#endif
return 0;
}
生产者生产物品,消费者消费物品,它们并行进行,我们没有编写创建线程的代码,没有构建共享内存,也没有处理线程的同步,这一切都很轻松的完成了。
5、代价和设计
和传统的多线程程序相比Theron有不少优势,通过使用Actor,程序能够自动的并行执行,而无需开发者费心,Actor总是利用消息进行通信,消息必须拷贝,这也意味着我们必须注意到,在利用Actor进行并行运算的同时需避免大量消息拷贝带来的额外开销。
Actor模型强调了一切皆为Actor,这自然可以作为我们使用Theron的一个准则,但过多的Actor存在必然导致Actor间频繁的通信。
适当的使用Actor并且结合Object模型也许会是一个不错的选择,例如,我们可以对系统进行适当划分,得到一些功能相对独立的模块,每个模块为一个Actor,模块内部依然使用Object模型,模块间通过Actor的消息机制进行通信。
马海祥博客点评:
Theron是个有趣的东西,也许你没有使用过它,你也不了解Actor模型,但是Actor的思想却不新鲜,甚至你可能正在使用,目前来说,我还没有找到Theron在哪个实际的商业项目中使用,因此对Theron的使用还存在一些未知的因素。
还有一些特性,诸如跨主机的分布式的并行执行是Theron不支持的,这些都限制了Theron的使用,不过我也正在积极的改变一些东西,无论Theron未来如何,Theron以及Actor模型带来的思想会让我们更加从容面对多核的挑战。
本文发布于马海祥博客文章,如想转载,请注明原文网址摘自于http://www.mahaixiang.cn/bcyy/954.html,注明出处;否则,禁止转载;谢谢配合!上一篇:你知道Java内部的这些事儿吗?
下一篇:哪些编程语言适合用于大数据集中处理?
您可能还会对以下这些文章感兴趣!
-
工作中令程序员最生气的10件事情
作为一个优秀的程序员,他的思想不能局限在当前的工作任务里面,要想想看自己写的模块是否可以脱离当前系统存在,通过简单的封装在其他系统中或其他模块中直接使用,这样做可以使代码能重复利用,减少重复的劳动,也能使系统结构越趋合理,模块化思维能力的提高是程序员……【查看全文】
-
初级软件工程师必须要学会哪些编程技术
软件工程师(Software Engineer)可以说是从事软件职业的人员的一种职业能力的认证,通过它说明具备了工程师的资格,通俗的来说,软件工程师就是从事软件开发相关工作的人员的统称,它是一个广义的概念,包括软件设计人员、软件架构人员、软件工程管理人员、程序员等一……【查看全文】
-
计算机语言的发展简史
计算机语言总的来说分为机器语言,汇编一语言,高级语言三大类。而这三种语言也恰恰是计算机语言发展历史的三个阶段。1946年2月14日,世界上第一台计算机ENIAC诞生,使用的是最原始的穿孔卡片。这种卡片上使用的语言是只有专家才能理解的语言,与人类语言差别极大,这种……【查看全文】
-
一个美国程序员对IT行业招聘的吐槽
今天听到一个朋友抱怨说“作为程序员,找工作有时候似乎挺苦逼的。”说真的,让我去掉前面这句中“似乎”二字吧。就是苦逼!很多人都曾抱怨处在招聘的一方很糟糕??我们没有任何可靠的方式来甄别会写代码并且写得好的人。这的确是真的,我们这行在这方面做得很糟糕。即……【查看全文】
-
盘点史上最奇葩的10大编程语言排行榜
一般来说,人们大多都认为编程语言很容易使用和学习,因为编程语言就是应该给你提供数据结构让你来解决实际问题,它们的语法也应清晰明了,容易理解且执行速度快,不应该有任何bug。但有时候编程语言设计者们会创建一些违背这些原则的语言,要么供研究使用,要么纯属娱……【查看全文】
-
为什么Swift编程语言刚一推出就这么火?
Swift语言是苹果公司在2014年WWDC(苹果开发者大会)上发布的全新开发语言,从演示视频及随后在appstore上线的标准文档看来,语法内容混合了OC,JS,Python,语法简单,使用方便,可与Objective-C*共同运行于MAC OS和iOS平台,用于搭建基于苹果平台的应用程序。作为一项……【查看全文】
-
Python语言代码的性能优化方法大全
选择了脚本语言就要忍受其速度,这句话在某种程度上说明了python作为脚本的一个不足之处,那就是执行效率和性能不够理想,特别是在performance较差的机器上,因此有必要进行一定的代码优化来提高程序的执行效率。那么我们该如何进行Python性能优化呢?接下来我就跟大家……【查看全文】
-
原来还能这样评价编程语言!
如果编程语言是女人,PHP是你的豆蔻年华的心上人,她是情窦初开的你今年夏天傻乎乎的追求的目标。玩一玩可以,但千万不要投入过深,因为这个女孩有严重的问题。Perl是PHP的姐姐。她对你来说年龄稍微大了一点,但在90年代,她是相当受欢迎的。她和Larry Wall(译注:Perl……【查看全文】
-
老程序员给初学者的一些建议和忠告
对于课程有这样简单的选择方法:如果你是计算机系的,请学好你所有的专业基础课;如果不是,请参照计算机系的课程表。如果你发现自己看一本书时无法看下去了,请翻到书的最后,看看它的参考文献,找到它们并学习它们,再回头看这本书。如果一本书的书名中带有“原理”……【查看全文】
-
你知道Java内部的这些事儿吗?
Java技术具有卓越的通用性、高效性、平台移植性和安全性,广泛应用于个人PC、数据中心、游戏控制台、科学超级计算机、移动电话和互联网,同时拥有全球最大的开发者专业社群,在全球云计算和移动互联网的产业环境下,Java更具备了显著优势和广阔前景。你是不是写Java已经……【查看全文】
阅读:659关键词: java 日期:2014-11-21
分类目录
互联网更多>>
基于高斯模糊原理的模糊图片的研究 高斯模糊(Gaussian Blur)的原理中,它是根据高斯曲线调节象素色值,它是有选择地模糊图像。说得直白一点,就是高……
互联网思维的一些特征 如今,互联网迅猛发展已经渗透人们生活各个方面,尤其是互联网正加快向传统行业渗透和融合,对传统行业提出严……
盘点2010年代这10年的重大网络安全事件 二十一世纪的第2个十年即将过去,在过去十年里有很多的重大网络安全事件发生,我们见证了过去十年,大量的数据……
SEO优化 更多>>
-
百度搜索将推出飓风算法2.0:严厉打
为了营造良好的搜索内容生态,保护搜索用户的…… -
网站收录量对于网站排名的影响作用
很多做SEO的朋友都会问,是不是网站收录越多网…… -
移动端手机站做站内优化的要点
随着移动流量的与日俱增,移动搜索引擎的功能…… -
SEO是什么?
对于刚入SEO行业的新人来说,要想做好网站的s…… -
百度冰桶算法5.0:保障移动搜索用户
为了提升移动搜索落地页体验,营造优质的移动…… -
今年网站SEO优化要注意的6大策略
通俗的来说,SEO技术就是一种达到SEO效果所采用…… -
如何使用留言评论进行推广引流
随着新媒体的快速发展,留言评论变得随处可见…… -
史上最全的网站SEO策略方案
在搜索引擎优化中,一个网站的SEO策略能最终影……