返回介绍

6.5 ZeroMQ

发布于 2023-05-19 13:36:37 字数 14650 浏览 0 评论 0 收藏 0

大家还记得 6.4 节中学到的那些提高工作效率的关键词吗?就是拖延和委派。所谓拖延,就是将工作分解成细小的任务,将无法马上着手的工作拖到后面再做,从而减少等待和无用的时间,提高整体的工作效率。在 6.4 节中,我们通过 node.js 这一彻底杜绝等待的非阻塞框架,对拖延策略进行了具体的实践。

然而,无论如何削减无谓的等待,在单位时间内,每个人所能完成的工作量,或者每个 CPU 所能处理的工作量,都是存在极限的。因此,我们需要另一个策略,即委派。也就是说,将工作交给多个人,或者多个 CPU 来共同分担,从而提升整体的处理效率。

接下来我们来具体学习一下委派策略。在编程中,委派就意味着充分运用多个 CPU 来进行分布式处理。

多 CPU 的必要性

对多 CPU 系统的需求主要出于两个原因:一个是 CPU 在摩尔定律影响下的发展趋势;另一个是对绝对性能的需求。

关于前者,在摩尔定律的影响下,一直以来 CPU 性能的提升都是通过晶体管数量的增加来实现的,但随着渗漏电流等问题所形成的障碍,这种传统的性能提升方式很快就会达到极限。于是,在一块芯片上搭载多个 CPU 核心的“多核”技术便应运而生。

最近的电脑中,多个程序同时工作的多任务方式已经成为主流,因此如果 CPU 拥有多个核心能够进行并行处理,那么必然会带来直接的性能提升。美国英特尔公司推出的 Core i7 CPU 搭载了 4 个物理核心,通过超线程技术,对外能够体现 8 个核心,面向普通电脑的 CPU 能做到这样的地步,已经着实令人惊叹了。

既然普通电脑都已经配备多核 CPU 了,那么积极运用这一技术来提高工作效率也是理所当然的事。然而,和过去 CPU 本身的性能提升不同,要想在多核环境下提升处理性能,软件方面也必须要支持多核才行。

在产生对多核系统需求的两个原因中,前者属于环境问题,即“存在多核 CPU,因此想要对其进行活用”这样的态度;而后者,即对绝对处理性能的需求,可以说是一种刚性需求了。有个词叫做“信息爆炸”,也就是说,我们每天所要接触的数据量正在不断增加。各种设备通过连接到电脑和网络,在不断获取新的信息,而原本孤立存在的数据,也将通过互联网开始相互流通。

在这一变化的影响下,我们每天所要接触到的数据量正在飞速增长。既然单个 CPU 的性能提升已经遇到了瓶颈,那么通过捆绑多个 CPU 来提升性能可以说是一个必然的趋势。

无论如何,可以说,要提升处理性能,充分运用多 CPU 是毫无悬念的,为此,必须要开发出能够活用多 CPU 的软件。

阿姆达尔定律

不过,首先大家必须要记住一点,从某种意义上说也是理所当然的,那就是即便为活用多 CPU 开发了相应的软件,也不可能无限地提升工作效率。

阿姆达尔定律说的就是这件事。前面我们已经讲过,阿姆达尔定律是由吉恩·阿姆达尔(Gene Amdahl)提出的,其内容是:“(通过并行计算所获得的)系统性能提升效果,会随着无法并行的部分而产生饱和”。

能够活用多 CPU 的处理,基本上可以分解为下列步骤:

1. 数据分割、分配

2. 对已分配的数据进行并行处理

3. 将已处理的数据进行集约

其中,能够并行处理的部分基本上只有 2,而数据的分割和集约无论有多少个 CPU,也无法最大限度地运用它们的性能。

多 CPU 的运用方法

运用多 CPU 的手段,大体上可以分成线程方式和进程方式两种。

除此之外,也有一些支持分布式编程的框架,但从内部原理来看,还是采用了线程、进程两种方式中的一种。

线程和进程都是多 CPU 的有效运用手段,但各自拥有不同的性质,也拥有各自的优点和缺点,应该根据应用程序的性质进行选择。

线程是在一个进程中工作的控制流程,其最大的特点是,工作在同一个进程内的多个线程之间,其内存空间是共享的。这一特点可以说是喜忧参半,却决定了线程的性格。

共享内存空间,就意味着在线程间操作数据时不需要进行复制。尤其是在线程间需要操作的数据非常多的情况下,像这样无需复制就能够传递数据,在处理性能方面是非常有利的。

然而,在获得上述好处的同时,也会带来一定的隐患。共享内存空间,也意味着某个线程中操作的数据结构,可能被其他线程修改。由于各线程是独立工作的,因此可能会导致一些特殊时机才出现的、非常难以发现的 bug。

虽然大多数情况下不会出问题,然而在非常偶然的情况下,两个线程同时访问同一数据结构,就会导致程序崩溃,而且这样的 bug 是很难重现的。一想到可能要去寻找这样的 bug,就会不由得感到眼前发黑。

此外,线程是在一个进程中工作的控制流程,反过来说,所有的处理都必须在同一个进程中完成,这也就意味着,如果要只采用线程方式来运用多 CPU,就必须在一台电脑上完成所有的处理。

然而,即便是在多核已经司空见惯的现在,一台电脑上能够使用的核心数量最多也就是 4 核,算上超线程也就是 8 核。服务器的话可能会配备更多的核心。但无论如何,现在还无法达到数百甚至数千核心的规模。如果要实现更高的并行度,仅靠线程还是会遇到极限的。

相对地,多进程方式同样是喜忧参半的,其特点正好和线程方式相反,即无法共享内存空间,但处理不会被局限在一台计算机上完成。

无法共享内存空间,就意味着在操作数据时需要进行复制操作,对性能有不利影响。但是,在并发编程中,数据共享一向是引发问题的罪魁祸首,因此从牺牲性能换取安全性的角度来说,也可以算是一个优点。

此外,刚才也提到过,在一台计算机所搭载的 CPU 数量不多的情况下,使用进程方式能够通过多台计算机构成的系统运用更多的 CPU 进行并行处理,这是一个很大的优点。不过,在这样的场景中,选择何种手段实现进程间的数据交换就显得非常重要。

尽管个人喜好可能并不可靠,但相比线程方式来说,我更加倾向于使用进程方式。

理由有两个。首先,要安全使用线程相当困难。相比共享内存带来的性能提升来说,由于状态的共享会导致一些偶发性 bug,因此风险大于好处。

其次,对性能提升带来的贡献,会受到该计算机中搭载的 CPU 核心数量上限的制约,因此其可扩展性相对较低。换句话说,可能搞得很辛苦,却得不到太多的好处,性价比不高。

当然,在某些情况下,线程方式比进程方式更合适,并不是说线程方式就该从世界上消失了。不过,我认为线程方式只应该用在有限的情况中,而且是用在一般用户看不见的地方,而不应该在应用程序架构模型的尺度上使用。当然,我知道一定有人不同意我的看法。

进程间通信

由于线程是共享内存空间的,因此不会发生所谓的通信。但反过来说,则存在如何防止多个进程同时访问数据的排他控制问题。

相对地,由于进程之间不共享数据,因此需要显式地进行通信。进程间通信的手段有很多种,其中具有代表性的有下列几种。

· 管道

· SysV IPC

· TCP 套接字

· UDP 套接字

· UNIX 套接字

下面我们来分别简单介绍一下。

管道

所谓管道,就是能够从一侧输出,然后从另一侧读取的文件描述符对。Shell 中的管道等也是通过这一方式实现的。

文件描述符在每个进程中是独立存在的,但创建子进程时会继承父进程中所有的文件描述符,因此它可以用于在具有父子、兄弟关系的进程之间进行通信。

例如,在具有父子关系的进程之间进行管道通信时,可以按下列步骤操作。在这里为了简单起见,我们只由子进程向父进程进行通信。

1. 首先,使用 pipe 系统调用,创建一对文件描述符。下面我们将读取一方的文件描述符称为“r”,将写入一侧的文件描述符称为“w”。

2. 通过 fork 系统调用创建子进程。

3. 在父进程一方将描述符 w 关闭。

4. 在子进程一方将描述符 r 关闭。

5. 在子进程一方将要发送给父进程的数据写入描述符 w。

6. 在父进程一方从描述符 r 中读取数据。

为了实现进程间的双向通信,需要按与上述相同的步骤创建两组管道。虽然比较麻烦,但难度不大。

和 Shell 一样,要在两个子进程之间进行通信,只要创建管道并分配给各子进程,各子进程之间就可以直接通信了。为了将进程与进程联系起来,每次都需要执行上述步骤,一旦自己亲自尝试过一次之后,就会明白 Shell 有多么强大了。

管道通信只能用于具有父子、兄弟关系、可共享文件描述符的进程之间,因此只能实现同一台电脑上的进程间通信。实际上,如果使用后面要介绍的 UNIX 套接字,就可以在不具有父子关系的进程之间传递文件描述符,但只能用在同一台电脑上的这一限制依然存在。

SysV IPC

UNIX 的 System V(Five)版本引入了一组称为 SysV IPC 的进程间通信 API,其中 IPC 就是 Inter Process Communication(进程间通信)的缩写。

SysV IPC 包括下列 3 种通信方式。

· 消息队列

· 信号量

· 共享内存

消息队列是一种用于进程间数据通信的手段。管道只是一种流机制,每次写入数据的长度等信息是无法保存的,相对地,消息队列则可以保存写入消息的长度。

信号量(semaphore)是一种带有互斥计数器的标志(flag)。这个词原本是荷兰语“旗语”的意思,在信号量中可以设定对某种“资源”同时访问数量的上限。

共享内存是一块在进程间共享的内存空间。通过将共享内存空间分配到自身进程内存空间中(attach)的方式来访问。由于对共享内存的访问并没有进行排他控制,因此无法避免一些偶发性问题,必须使用信号量等手段进行保护。

不过,说实话,我自己从来没用过 SysV IPC。原因有很多,最重要的一个原因是资源泄漏。由于 SysV IPC 的通信路径能够跨进程访问,因此在使用时需要向操作系统申请分配才能进行通信,通信完全结束之后还必须显式销毁,如果忘记销毁的话,就会在操作系统内存中留下垃圾。相比之下,管道之类的方式,在其所属进程结束的同时会自动销毁,因此比 SysV IPC 要更加易用。

其次,学习使用新的 API 要花一些精力,但结果也只能用在一台电脑上的进程间通信中,真是让人没什么动力去用呢。

最后一个原因,就是在 20 多年前我开始学习 UNIX 编程的时候,并非所有的操作系统都提供了这一功能。当时,擅长商用领域的 AT&T 系 System V UNIX 和加州大学伯克利分校开发的 BSD UNIX 正处于对峙时期。那个时候,我主要用的是 BSD 系 UNIX,而这个系统就不支持 SysV IPC。现在大多数 UNIX 系操作系统,包括 Linux,都支持 SysV IPC 了,但过去则并非如此,也许正是这种历史原因造成我一直都没有去接触它。

后来,System V 与 BSD 之间的对峙,随着双方开始吸收对方的功能而逐步淡化,再往后,严格来说,不属于 System V 和 BSD 两大阵营的 Linux 成为了 UNIX 系操作系统的最大势力,而曾经的对峙也成为了历史,这个结局恐怕在当时是谁都无法想象的吧。

说到底,用都没用过的东西要给大家介绍实在是难上加难。关于 SysV IPC 的用法,大家可以在 Linux 中参考一下:

# man svipc
其他操作系统中,也可以从创建消息队列的 msgget 系统调用的 man 页面中找到相关信息。

套接字

System V 所提供的进程间通信手段是 SysV IPC,相对地,BSD 则提供了套接字的方式。和其他进程间通信方式相比,套接字有一些优点。

· 通信对象不仅限于同一台计算机,或者说套接字本身主要就是为计算机间的通信而设计的。

· (和 SysV IPC 不同)套接字也是一种文件描述符,可进行一般的输入输出。尤其是可以使用 select 系统调用,在通常 I/O 的同时进行“等待”,这一点非常方便。

· 套接字在进程结束后会由操作系统自动释放,因此无需担心资源泄漏的问题。

· 套接字(由于其优秀的设计)从很早开始就被吸收进 System V 等系统了,因此在可移植性方面的顾虑较少。

现代网络几乎完全依赖于套接字。各位所使用的几乎所有服务的通信都是基于套接字实现的,这样说应该没有什么大问题。

套接字分为很多种,其中具有代表性的包括:

· TCP 套接字

· UDP 套接字

· UNIX 套接字

TCP(Transmission Control Protocol,传输控制协议)套接字和 UDP(User Datagram Protocol,用户数据报协议)套接字都是建立在 IP(Internet Protocol,网际协议)协议之上的上层网络通信套接字。这两种套接字都可用于以网络为媒介的计算机间通信,但它们在性质上有一些区别。

TCP 套接字是一种基于连接的、具备可靠性的数据流通信套接字。所谓基于连接,是指通信的双方是固定的;而所谓具备可靠性,是指能够侦测数据发送成功或是发送失败(出错)的状态。

所谓数据流通信,是指发送的数据是作为字节流来处理的,和通常的输入输出一样,不会保存写入的数据长度信息。

看了上面的内容,大家可能觉得这些都是理所当然的嘛。我们和 UDP 套接字对比一下,就能够理解其中的区别了。

UDP 套接字和 TCP 套接字相反,是一种能够无需连接进行通信、但不具备可靠性的数据报通信套接字。所谓能够无需连接进行通信,是指无需固定连接到指定对象,可以直接发送数据;不具备可靠性,是指可能会出现中途由于网络状况等因素导致发送数据丢失的情况。

在数据报通信中,发送的数据在原则上是能够保存其长度的。但是,在数据过长等情况下,发送的数据可能会被分割。

先不说无连接通信这一点,UDP 的其他一些性质可能会让大家感到非常难用。这是因为 UDP 几乎是原原本本直接使用了作为其基础的 IP 协议。相反,TCP 为了维持可靠性,在 IP 协议之上构建了各种机制。UDP 的特点是结构简单,对系统产生的负荷也较小。

因此,在语音通信(如 IP 电话等)中一般会使用 UDP,因为通信性能比数据传输的可靠性要更加重要,也就是说,相比通话中包含少许杂音来说,还是保证较小的通话延迟要更加重要。

TCP 套接字和 UDP 套接字都是通过 IP 地址和端口号来进行工作的。例如,http 协议中的“http://www.rubyist.net:80/”就表示与 www.rubyist.net(2012 年 3 月 27 日当时的 IP 地址为 221.186.184.67)所代表的计算机的 80 号端口建立连接。

UNIX 套接字

同样是套接字,UNIX 套接字和 TCP、UDP 套接字相比,可以算是一个异类。基于 IP 的套接字一般是通过主机名和端口号来识别通信对象的,而 UNIX 套接字则是在 UNIX 文件系统上创建一个特殊文件,并用该文件的路径进行识别。由于这种方式使用的是文件系统,因此大家可以看出,UNIX 套接字只能用于同一台计算机上的进程间通信。

UNIX 套接字并不是基于 IP 的套接字,它可用于向同一台计算机上其他进程提供服务的某种服务程序。例如有一种叫做 canna 的汉字转换服务,就是通过 UNIX 套接字来接受客户端连接的。

ZeroMQ

在进程间通信手段中,套接字算是非常好用的,但即便如此,在考虑对工作进行“委派”时,其易用性还并不理想。套接字本来是为网络服务器的实现而设计的,但作为构建分布式应用程序的手段来说,却显得有些过于原始了。

ZeroMQ 就是为了解决这一问题而诞生的,它是一种为分布式应用程序开发提供进程间通信功能的库。

ZeroMQ 的特点在于灵活的通信手段和丰富的连接模型,并且它可以在 Linux、Mac OS X、Windows 等多种操作系统上工作,也支持由多种语言进行访问。ZeroMQ 所支持的语言列表如表 1 所示。

表1 ZeroMQ支持的语言一览

Ada

Lua

CommonLisp

Perl

BASIC

Node.js

Erlang

Python

C

Objective-C

Go

Racket

C#

ooc

Haskell

Ruby

C++

PHP

Java

Scala

ZeroMQ 提供了下列底层通信手段。无论使用哪种手段,都可以通过统一的 API 进行访问,这一点可以说是 ZeroMQ 的魅力。

· tcp

· ipc

· inproc

· multicast

tcp 就是 TCP 套接字,它使用主机名和端口号进行连接。根据 TCP 套接字的性质,从其他计算机也可以进行连接,但由于 ZeroMQ 不存在身份认证这样的安全机制,因此建议大家不要在互联网上公布 ZeroMQ 的端口号。

ipc 用于在同一台计算机上进行进程间通信,使用文件路径来进行连接。实际通信中使用何种方式与实现有关,在 UNIX 系操作系统上采用的是 UNIX 套接字,在 Windows 上也许是用一般套接字来通信的吧。

inproc 用于同一进程中的线程间通信。由于线程之间是共享内存空间的,因此这种通信方 式是无需复制的。使用 inproc 通信,可以在活用线程的同时,避免麻烦的数据共享,不仅通信效率高,编写的程序也比较易读。

multicast 是一种采用 UDP 实现的多播通信。为了实现一对多的通信,如果使用一对一的 TCP 方式,则需要对多个对象的 TCP 连接反复进行通信,但如果使用原本就用于多播通信的 multicast,就可以避免无谓的重复操作。

不过,UDP 通信,尤其是多播传输,在一些路由器上是被禁止的,因此这种方式并不能所向披靡,这的确是个难点。

ZeroMQ 的连接模型

ZeroMQ 为分布式应用程序的构建提供了丰富多彩的连接模型,主要有以下这些。

· REQ/REP

· PUB/SUB

· PUSH/PULL

· PAIR

REQ/REP 是 REQUEST/REPLY 的缩写,表示向服务器发出请求(request),服务器向客户端返回应答(reply)这样的连接模型(图 1)。

图 1 REQ/REP 模型

作为网络连接来说,这种方式是非常常见的。例如 HTTP 等协议,就遵循 REQ/REP 模型。通过网络进行函数调用的 RPC(Remote Procedure Call,远程过程调用)也属于这一类。REQ/REP 是一种双向通信。

PUB/SUB 是 PUBLISH/SUBSCRIBE 的缩写,即服务器发布(publish)信息时,在该服务器上注册(subscribe,订阅)过的客户端都会收到该信息(图 2)。这种模型在需要向大量客户端一起发送通知,以及数据分发部署等场合非常方便。PUB/SUB 是一种单向通信。

图 2 PUB/SUB 模型

PUSH/PULL 是向队列中添加和取出信息的一种模型。PUSH/PULL 模型的应用范围很广,如果只有一个数据添加方和一个数据获取方的话,可以用类似 UNIX 管道的方式来使用(图 3a),如果是由一台服务器 PUSH 信息,而由多台客户端来 PULL 的话,则可以用类似任务队列的方式来使用(图 3b)。

图 3 PUSH/PULL 模型

在图 3b 的场景中,处于等待状态的任务中只有一个能够取得数据。相对地,PUB/SUB 模型中则是所有等待的进程都能够取得数据。

反过来说,如果有多个进程来 PUSH,则能够用来对结果进行集约(图 3c)。和 PUB/SUB 一样,PUSH/PULL 也是一种单向通信。

PAIR 是一种一对一的双向通信。说实话,在我所了解的范围内,还不清楚这种模型应该如何使用。

ZeroMQ 的安装

首先安装 ZeroMQ 库的主体。在 Debian 中提供的软件包叫做 libzmq-dev,安装方法如下:

$ apt-get install libzmq-dev
如果在你所使用的平台上没有提供二进制软件包,也可以从 http://www.zeromq.org/ 下载源代码进行编译安装。截止到 2012 年 3 月 27 日,其最新版本为 2.1。

ZeroMQ 的标准 API 是以 C 语言方式提供的,但 C 语言实在太繁琐了,因此这里的示例程序我们用 Ruby 来编写。Ruby 的 ZeroMQ 库叫做 zmq,可以通过 RubyGems 进行安装。在安装 ZeroMQ 基础库之后,运行

$ gem install zmq
即可安装 ZeroMQ 的 Ruby 库。

ZeroMQ 示例程序

首先,我们来看看最简单的 REQ/REP 方式。图 4 是用 Ruby 编写的 REQ/REP 服务器。在这里我们只接受来自本地端口的请求,如果将 127.0.0.1 的部分替换成“*”就可以接受来自任何主机的请求了。客户端程序如图 5 所示。

require 'zmq'

context = ZMQ::Context.new
socket = context.socket(ZMQ::REP)
socket.bind("tcp://127.0.0.1:5000")

loop do
  msg = socket.recv
  print "Got ", msg, "\n"
  socket.send(msg)
end
图 4 ZeroMQ REQ/REP 服务器

require 'zmq'

context = ZMQ::Context.new
socket = context.socket(ZMQ::REQ)
socket.connect("tcp://127.0.0.1:5000")

for i in 1..10
  msg = "msg %s" % i
  socket.send(msg)
  print "Sending ", msg, "\n"
  msg_in = socket.recv
  print "Received ", msg, "\n"
end
图 5 ZeroMQ REQ/REP 客户端

这样我们就完成了一个万能 echo 服务器及其相应的客户端。

ZeroMQ 可以发送和接收任何二进制数据,如果我们发送 JSON 和 MessagePack 字符串的话, 就可以轻松实现一种 RPC 的功能。

要进行通信,可以按顺序启动图 4 和图 5 的程序。有意思的是,一般的套接字程序中,必须先启动服务器,但 ZeroMQ 程序中,先启动客户端也是可以的。

ZeroMQ 是按需连接的,因此当连接对象尚未初始化时,客户端会进入待机状态。启动顺序自由这一点非常方便,尤其是 PUB/SUB 和 PUSH/PULL 模型的连接中,如果所有的服务器和客户端只能按照一定顺序来启动,那制约就太大了,而 ZeroMQ 则可以将我们从这样的制约中解放出来。

此外,ZeroMQ 还可以同时连接多个服务器。如果在图 5 程序的第 5 行,即 connect 那一行之后,再添加一行相同的语句(例如只改变端口号),就可以对两个服务器交替发送请求。通过这样的方式,可以很容易实现负载的分配。

下面我们再来看看用 PUSH/PULL、PUB/SUB 模型实现的一个简单的聊天程序。这个示例由 3 个程序构成。程序 1(图 6)是聊天发言用的程序。通过将命令行中输入的字符 PUSH 给服务器来“发言”。程序 2(图 7)是显示发言用的程序。通过 SUBSCRIBE 的方式来获取服务器 PUBLISH 的发言信息,并显示在屏幕上。实际的聊天系统中,客户端应该是由程序 1 和程序 2 结合而成的。

require 'zmq'

context = ZMQ::Context.new
socket = context.socket(ZMQ::PUSH)
socket.connect("tcp://127.0.0.1:7900")

socket.send(ARGV[0])
图 6 聊天发言程序

require 'zmq'

context = ZMQ::Context.new
socket = context.socket(ZMQ::SUB)
socket.connect("tcp://127.0.0.1:7901")
# 显示执行SUBSCRIBE操作并对消息进行取舍选择
# 空字符串表示全部获取的意思
socket.setsockopt(ZMQ::SUBSCRIBE, "")

loop do
  puts socket.recv
end
图 7 聊天显示程序

程序 3(图 8)是聊天服务器,它通过 PULL 来接收发言数据,并将其原原本本 PUBLISH 出去,凡是 SUBCRIBE 到该服务器的客户端,都可以收到发言内容(图 9)。无论有多少个客户端连接到服务器,ZeroMQ 都会自动进行管理,因此程序的实现就会比较简洁。

require 'zmq'

context = ZMQ::Context.new
receiver = context.socket(ZMQ::PULL)
receiver.bind("tcp://127.0.0.1:7900")
clients = context.socket(ZMQ::PUB)
clients.bind("tcp://127.0.0.1:7901")

loop do
  msg = receiver.recv
  printf "Got %s¥n", msg
  clients.send(msg)
end
图 8 聊天服务器程序

图 9 聊天程序的工作方式

小结

ZeroMQ 是一个用简单的 API 实现进程间通信的库。和直接使用套接字相比,它在一对多、多对多通信的实现上比较容易。在对多 CPU 的运用中,横跨多台计算机的多进程间通信是不可或缺的,因此在需要考虑可扩展性的软件开发项目中,像 ZeroMQ 这样的进程间通信库,今后应该会变得越来越重要。

“多核时代的编程”后记

有一句话在这本书中说过很多次,各位读者可能也已经听腻了,不过在这里我还是想再说一次:现在是多核时代。

所谓多核,原本是指在一块芯片上封装多个 CPU 核心的意思。截至 2012 年 4 月,一般能够买到的电脑基本上都搭载了 Intel Core i5 等多核 CPU 芯片,这一事实也是这个时代的写照。

本书中所说的多核,并不单指多核 CPU 的使用,大多数情况下指的是“运行一个软件系统可以利用多个 CPU 核心”这个意思。在这样的场景中,并不局限于一块芯片。由多块芯片,甚至是多台计算机组成的环境,也可以看作是多核。按照这样的理解,云计算环境可以说是一种典型的多核环境吧。

多核环境中编程的共同点在于,在传统的编程风格中,程序是顺序执行的,因此只能用到单独一个核心。而要充分发挥多核的优势,就必须通过某些方法,积极运用多个 CPU 的处理能力。

本书中介绍了一些活用多个 CPU 的方法,包括 UNIX 进程的活用、通过异步 I/O 实现并行化、消息队列等,这些都是非常有前途的技术。然而,UNIX 进程(在基本的使用方法中)只能用在一台计算机中;而异步 I/O 虽然能提高效率,但其本身无法运用多核;消息队列目前也没有强大到能够支持数百、数千节点规模系统的构建。

从超级计算机的现状进行推测,在不远的将来,云计算环境中的“多核系统”就能够达到数万节点、数十万核心的规模。要构建这样的系统,用现在的技术是可以实现的,但并非易事。

因此,在这一方面,今后还需要更大的进步。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文