----------------------- Page 1-----------------------

----------------------- Page 2-----------------------
数字版权声明
图灵社区电子书没有采用专有
可以任意设备
喜欢浏览器PDF阅读器进行
阅读
购买电子书仅供个人使
未经授权不得进行传播
我们愿意相信读者具有这样良知
觉悟我们共同保护知识

如果购买者侵权行为我们可能
用户实施包括限于关闭
帐号维权措施可能追究法律
责任

----------------------- Page 3-----------------------

----------------------- Page 4-----------------------

----------------------- Page 5-----------------------

Ruby 本行力作作者云计算大数据时代各种编程语言以及相关
进行剖析编程语言未来发展趋势做出预测内容涉及 Go、VoltDB 、node.js 、CoffeeScript、
Dart 、MongoDB 、
摩尔定律编程语言、NoSQL 当今备受关注的话
书面层次程序设计人员编程爱好者相关技术人员参考
图灵程序设计丛书
代码未来
    [ ] 本行
    日经Linux
    
责任编辑 
执行编辑 
责任印制 
封面设计 石田/株式会社マップス
       (MASAHARU ISHIDA/MAPS Co.,Ltd. )
人民邮电出版社出版发行  北京市崇文区夕照 14
邮编 100061  电子邮件 315@ptpress.com.cn
网址 http://www.ptpress.com.cn
北京      印刷
开本 :800 ×1000  1/16
:23
字数 :544 2013 5 1
印数:1 — 5 000 2013 5 北京 1 印刷
著作权合同登记 :01-2013-1117
ISBN 978-7-115-31751-3
定价 :79.00
读者服务热线: 010 51095186604 质量热线: 010 67129223( ) ( )
盗版热线: 010 67171154( )

----------------------- Page 6-----------------------

依靠简洁优雅语言特色以及 Rails 开发框架成功,Ruby
Web 开发领域早已成为一种人气动态脚本语言然而当今世界
上流编程语言只有 Ruby 来自亚洲作为 Ruby 语言发明者
本行(Matz )表示自己因此感到孤独
作为译者,2012 11 中国 Ruby 大会机会有幸
图灵特派记者身份 Matz 进行 一次专访① 。穿着 UNIQLO 格子
衬衫充满技术范儿 Matz ,平时看起来不苟谈起技术话题
好像打开话匣子一般滔滔不绝 Twitter 发言相当活跃访
,Matz 谈到 Ruby 发展方向希望 Ruby 能够 Web 开发之外
领域科学计算高性能计算嵌入系统发展同时
希望 中国程序员能够积极开源社区做出贡献努力成为能够影响
工程师
Matz
一直自己普通程序员创造 Ruby 只不过编程
生涯中的一小部分无论是以资深 UNIX 程序员身份还是“Ruby
身份,Matz 足够资格现今编程语言技术品头论足
另一方面计算机技术发展可谓日新月异,Matz 认为必要过去
发展眼光看待这些技术演进资深程序员视角发展
眼光剖析技术就是 Matz 笔下代码未来》。
,Matz 大家一起探讨丰富多彩技术话题编程
语言未来发展趋势做出自己预测 Lisp 这样拥有核心函数
语言真的未来发展趋势垃圾回收闭包高阶函数编程
编程语言中的要素如何发展出来? Google 为什么开发 Go Dart ,
C JavaScript Hadoop 、
MapReduce 、NoSQL
名词到底什么意思关系数据库真的已经
穷途末路充分运用核心分布式环境软件层面需要做出
应对哪些技术可以使用如果上面这些话题感兴趣无论
心中是否已经有了自己答案可以看一看来自 Matz 解读
访谈内容参见图灵社区:http://www.ituring.com.cn/article/17487 。

----------------------- Page 7-----------------------
  
本行程序世界一样 Matz 日经 Linux 》杂志连载专栏文章
合集选取文章之间时间跨度章节安排原稿写作时间
有所不同 了解这个背景读者可能一些貌似前后重复或者穿越地方
一头雾水——少安毋躁不是 bug 。相比本行程序世界 14 主题
主题更加中和深入不变话题依然丰富观点依然犀利内容依然扎实
畅快淋漓
最后感谢 Matz 翻译过程给予帮助指导感谢图灵公司各位编辑辛苦
希望读者能够从中有所收获

2013
3 上海
4
----------------------- Page 8-----------------------
中文
人类力量有限无法完全通晓未来因此我们并不确切
明天明年究竟发生什么事
不过技术一夜之间东西这样情况非常
罕见 大多数新技术沿着过去到现在技术轨迹逐步发展起来
IT 世界这样倾向尤其显著
代码未来综述当前掌握 IT 趋势摩尔定律编程
语言、NoSQL 未来几年中将备受关注领域介绍相关
现状基础知识
当然知道涉及这些技术久远未来是否依然有用
至少将来它们应该非常值得关注技术这些内容可以
学习新技术基础对于想要成为优秀工程师程序员各位读者
这样基础能够成为生存竞争中的有力武器
也许还有一些读者并非专职程序员认为同样值得他们
所谓技术就是用来解决现实问题手段现实问题展开
本身就是非常刺激快乐快乐正是带动未来
创新动力
互联 开源降低参与创新门槛即便没有高学历即便属于
任何一家企业只要技术点子有机可以想象未来创新
这样 IT 方面认为大多数创新应该不外乎
这些技术延伸
有人 21 世纪亚洲世纪作为亚洲开发 Ruby 语言
已经全世界获得广泛应用也许某种程度印证这种说法
包含一些思考见解如果能够亚洲恐怕应该
各位读者创新有所帮助感到荣幸之至
最后希望中国各位读者能够获益
本行
2013
4

----------------------- Page 9-----------------------
 
日经 Linux 》连载本行技术剖析》(2009
6 ~ 2012 6 内容合集
老实说文章头疼认为自己本职工作程序
不是作家构思主题查阅资料编写示例程序然后
文章对我来说真是负担时间占用拖累本职工作
截稿日前承受压力因此一阵子经常感到无比焦虑
话虽如此并非一无是处构思文章主题时候需要
放眼日常工作以外世界这样便拓宽视野其实本来不是
那么讨厌文章起来学生时代成绩最好科目还是语文英语
科目数学
因为杂志社供稿 选择当时那个时间点比较
热门能够引起兴趣的话没有考虑主题连贯
编辑成书机会回过头来看看以前连载文章编辑讨论之后
头脑 便一下子浮现未来这个关键词连载中的文章原本
独立它们中的大多数体现过去未来”、“应对即将到来
未来这样主题作为这些文章作者自己感到颇为意外
毋庸置疑,IT 技术正在创造我们现在未来无论专业人士
还是业余爱好者我们这样 IT 技术可以未来遭遇
人种正是为了这些技术剖析这个专题连载
这些连载浮现未来这个共同关键词虽说事先没有预料
某种意义上来也许水到渠成自然而然结果
然而,IT 技术真正价值应该并非只有未来遭遇而已
我们不仅能够及早触及未来应该拥有自己创造未来力量——创造
预见未来更加美好未来
本行
2012
4
樱花盛开松江

----------------------- Page 10-----------------------
  
1 编程时间空间
1.1 
编程本质 ..............................................................................3
编程本质思考 / 4 以不变应万变 / 8
创造世界乐趣 / 4 摩尔定律局限 / 9
快速提高性能改变社会 / 5 社会变化编程 / 10
1.2 
未来预测 ................................................................................13
科学未来预测 / 14 性能未来 / 17
IT
未来预测 / 14 容量未来 / 18
极限未来预测 / 16 带宽未来 / 19
价格未来 / 16 小结 / 20
2 编程语言过去现在未来
2.1 
编程语言世界 .....................................................................23
历史埋没先驱 / 25 未来编程语言 / 33
编程语言历史 / 26 20 编程语言 / 34
编程语言进化方向 / 31 学生想象 / 35
2.2 DSL (
特定领域语言) ..........................................................36
外部 DSL / 37 外部 DSL 实例 / 42
内部 DSL / 38 DSL 设计构成要素 / 43
DSL
优势 / 39 Sinatra / 46
DSL
定义 / 39 小结 / 47
适合内部 DSL 语言 / 40
2.3 
编程 ...................................................................................48
Meta, Reflection / 48 Lisp
程序 / 56
对象 / 51 / 56
操作 / 52 / 57
Lisp / 53
编程可能性危险 / 59
数据程序 / 54 小结 / 60
7
----------------------- Page 11-----------------------
  
2.4 
内存管理 ................................................................................61
看似无限内存 / 61 进一步改良应用方式 / 66
GC
基本方式 / 62 回收 / 66
术语定义 / 62 来自老生引用进行记录 / 67
标记清除方式 / 63 增量回收 / 68
复制收集方式 / 64 并行回收 / 69
引用计数方式 / 65 GC 统一理论 / 69
引用计数方式缺点 / 65
2.5 
异常处理 ................................................................................71
一定没问题” / 71 其他语言中的异常处理 / 77
特殊返回表示错误 / 72 Java 检查异常 / 77
容易忽略错误处理 / 72 Icon 异常真假 / 78
Ruby
中的异常处理 / 73 Eiffel Design by Contract / 80
产生异常 / 74 异常错误 / 80
高级异常处理 / 75 小结 / 81
Ruby
中的处理保证 / 76
2.6 
闭包 .......................................................................................82
函数对象 / 82 生存周期变量存在范围 / 88
高阶函数 / 83 闭包面向对象 / 89
函数参数提高通用性 / 84 Ruby 函数对象 / 89
函数指针局限 / 85 Ruby JavaScript 区别 / 90
作用变量可见范围 / 87 Lisp-1 Lisp-2 / 91
3 编程语言潮流
3.1 
语言设计 ............................................................................97
客户端服务器 / 97 静态动态 / 104
服务器华丽转身 / 98 动态运行模式 / 105
服务器获得成功四大理由 / 99 何谓类型 / 105
客户端的 JavaScript / 100 静态类型优点 / 106
性能显著提升 / 101 动态类型优点 / 106
服务器端的 Ruby / 102 鸭子就是鸭子 / 107
Ruby on Rails
带来飞跃 / 102 Structural Subtyping / 108
服务器端的 Go / 103 小结 / 108
8
----------------------- Page 12-----------------------
  
3.2 Go ....................................................................................... 109
New(
) / 109 Go 控制结构 / 113
Experimental(
实验) / 109 类型声明 / 116
Concurrent(
并发) / 110 继承面向对象 / 118
Garbage-collected(
垃圾回收) / 110 多值多重赋值 / 120
Systems(
系统) / 111 并发编程 / 122
Go
创造者 / 111 小结 / 124
Hello World / 112
3.3 Dart ..................................................................................... 126
为什么推出 Dart ? / 126 基于对象系统 / 132
Dart
设计目标 / 129 强制静态类型 / 133
代码示例 / 130 Dart 未来 / 134
Dart
特征 / 132
3.4 CoffeeScript ........................................................................ 135
普及语言 / 135 分号代码 / 141
误解最多语言 / 135 省略记法 / 142
显著高速语言 / 136 字符串 / 143
JavaScript 不满 / 138 数组循环 / 143
CoffeeScript / 138
/ 145
安装方法 / 139 小结 / 146
声明作用 / 139
3.5 Lua ...................................................................................... 148
示例程序 / 149 基于原型编程 / 155
数据类型 / 149 Ruby 比较语言) / 157
函数 / 150 嵌入语言 Lua / 157
/ 150 Ruby 比较实现) / 158
/ 151 嵌入 Ruby / 159
方法调用实现 / 153
4 云计算时代编程
4.1 
扩展 ..............................................................................163
信息尺度 / 163 列表 / 167
大量数据查找 / 164 过滤器 / 169
二分查找 / 165 计算机极限 / 170
9
----------------------- Page 13-----------------------
  
DHT(
分布式列表) / 171 MapReduce / 173
Roma / 172
小结 / 174
4.2 C10K
问题 ........................................................................... 175
何为 C10K 问题 / 175 使用 libev 框架 / 181
C10K
问题引发想当然” / 177 使用 EventMachine / 183
使用 epoll 功能 / 180 小结 / 185
4.3 HashFold.............................................................................186
HashFold
实现(Level 1) / 187 抖动 / 193
运用必要性 / 190 运用进程 HashFold(Level 3) / 194
目前 Ruby 实现存在问题 / 191 小结 / 197
通过进程实现 HashFold(Level 2) / 191
4.4 
进程通信 .......................................................................... 198
进程线程 / 198 Ruby 进行编程 / 204
同一计算机进程通信 / 199 Ruby 功能 / 205
TCP / IP
协议 / 201 Ruby 实现网络服务器 / 208
C 语言进行编程 / 202 小结 / 209
4.5 Rack
Unicorn ..................................................................210
Rack
中间件 / 211 性能 / 219
应用程序服务器问题 / 212 策略 / 220
Unicorn
架构 / 215 小结 / 221
Unicorn
解决方案 / 215
5 支撑大数据数据存储技术
5.1 
- 存储 ..........................................................................225
Hash
/ 225 存储器 / 232
DBM
/ 226 读取 / 233
数据库 ACID 特性 / 226 节点追加 / 233
CAP
原理 / 227 故障应对 / 233
CAP
解决方案——BASE / 228 终止处理 / 235
不能舍弃 / 229 其他机制 / 235
大规模环境 - 存储 / 230 性能应用实例 / 236
访问 - 存储 / 230 小结 / 236
- 存储节点处理 / 231
10
----------------------- Page 14-----------------------
  
5.2 NoSQL ................................................................................237
RDB
极限 / 237 MongoDB 数据库结构 / 244
NoSQL
数据库解决方案 / 238 数据插入查询 / 244
形形色色 NoSQL 数据库 / 239 JavaScript 进行查询 / 245
面向文档数据库 / 240 高级查询 / 246
MongoDB
安装 / 241 数据更新删除 / 249
启动数据库服务器 / 243 乐观并发控制 / 250
5.3 
Ruby 操作 MongoDB ..................................................251
使用 Ruby 驱动 / 251 find 方法选项 / 256
对数进行操作 / 253 原子操作 / 257
数据插入 / 253 ActiveRecord / 259
数据查询 / 253 OD Mapper / 260
高级查询 / 254
5.4 SQL
数据库反击 ..............................................................264
定义 / 264 VoltDB 架构 / 269
SQL
数据库极限 / 264 VoltDB 中的编程 / 270
存储引擎 Spider / 265 Hello VoltDB! / 271
SQL
数据库反驳 / 265 性能测试 / 273
SQL
数据库 VoltDB / 268 小结 / 275
5.5 memcached
伙伴 ..................................................276
用于高速访问缓存 / 276 一种 - 存储 Redis / 282
memcached / 277 Redis
数据类型 / 284
示例程序 / 278 Redis 命令示例 / 285
memcached 不满 / 279 小结 / 289
memcached
替代服务器 / 280
6 时代编程
6.1 
摩尔定律 ..............................................................................293
几何级数增长 / 293 为了提高性能 / 297
摩尔定律内涵 / 294 摩尔定律极限 / 302
摩尔定律结果 / 295 超越极限 / 303
摩尔律所带来可能性 / 296 不再免费午餐 / 304
11
----------------------- Page 15-----------------------
  
6.2 UNIX
管道 ...........................................................................305
管道编程 / 306 编译 / 312
时代管道 / 308 ccache / 313
xargs——
一种运用核心方式 / 309 distcc / 313
注意瓶颈 / 311 编译性能测试 / 314
定律 / 311 小结 / 315
6.3 
阻塞 I / O .........................................................................316
何为阻塞 I / O / 316 使用 read+O_NONBLOCK 标志 / 321
使用 read(2) 方法 / 317 Ruby 阻塞 I / O / 322
边沿触发触发 / 319 使用 aio_read 方法 / 323
使用 read(2) + select 方法 / 319
6.4 node.js ................................................................................330
减负 / 330 事件循环利弊 / 335
拖延 / 331 node.js 编程 / 335
委派 / 332 node.js 网络编程 / 337
阻塞编程 / 333 node.js 回调风格 / 339
node.js
框架 / 333 node.js 优越 / 340
事件驱动编程 / 334 EventMachine Rev / 341
6.5 ZeroMQ ...............................................................................342
CPU 必要性 / 342 UNIX / 349
定律 / 343 ZeroMQ / 349
CPU 运用方法 / 343 ZeroMQ 连接模型 / 350
进程通信 / 345 ZeroMQ 安装 / 352
管道 / 345 ZeroMQ 示例程序 / 352
SysV IPC / 346
小结 / 354
/ 347
版权声明 ......................................................................................356
12
----------------------- Page 16-----------------------
编程时间空间 1

----------------------- Page 17-----------------------
1.1
编程本质
4 ① 23
古老电影星际迷航抢救未来这样镜头 世纪未来穿越
时空来到现代(1986 进取乘务员为了操作计算机(Classic Mac )鼠标
讲话看来星际迷航世界人类语言作为操作界面可以指挥计算机工作
不过现代计算机无法完全理解人类语言市面上有一些可以日语操作
距离实用程度得很计算机本来为了运行 0 1 组成机器语言设计
与此同时对于人类理解这种二进制构成序列到底代表什么意思却是非常
困难
因此创造一种人类计算机能够理解语言编程语言),通过这样语言人类
意图传达计算机这样行为叫做编程
话虽如此但是编程仅仅认为因为计算机无法理解人类语言产生替代品”,
合适人类语言其实非常模糊有时根本不符逻辑
Time flies like an arrow.
意思 ”(时间一样),不过 flies 苍蝇”(复数形态
意思因此如果非要解释未尝不可只要纠结到底
这种朴素问题好了
另一方面自然语言人类语言不同编程语言设计时候避免模糊
不会产生这样歧义使用编程语言可以步骤更加严密描述出来
编程语言计算机需要执行操作步骤详细描述出来成了软件计算机软件
文字处理工具 Web 浏览器这样大型软件还是操作系统这样底层软件全部
编程语言编写出来
电影 Star Trek IV: The Voyage Home ,著名星际迷航系列科幻电影 4 作品上映 1986
特殊说明脚注译者
3
----------------------- Page 18-----------------------
1 编程时间空间
编程本质思考
由于几乎整天计算机因此家人可能认为工作计算机打交道然而
编程这个行为理解计算机传达处理内容片面这样理解方式实际
状态并不完全一致
的确程序员计算机工作作为
本质什么
人们到底 成果软件 大部分为了完成人类
想要什么
工作设计出来 1 )。因此,“人们到底想要
实现步骤
什么什么想要这些东西本质什么实现这个目的
严格来说需要怎样操作步骤?”思考解决这些问题
软件开发重要工作换句话说编程本质
在于思考”。
尽管看上去计算机打交道工作实际上编程
1 编程不是计算机打交道而是
打交道 对象还是人类因此非常有人工作
认为编程需要完成工作因此相信
计算机可以自己编程
初三时候开始接触编程当时父亲夏普袖珍计算机(PC-1210 ),
使用 BASIC 编程虽然袖珍计算机只能输入 400 步骤看到计算机可以按照
命令运行仿佛自己什么做到一种万能便油然而生
创造世界乐趣
尽管已经过去 20 多年编程活动感到心潮澎湃却是有增无减
这种心潮澎湃感觉是不是创造世界行为产生喜欢编程多少年来
从未厌倦其中理由就是因为编程看作创造性工作
只要有了计算机这个工具可以从零开始创造世界编程世界基本上
现实世界重力因果关系这样制约如此自由创造活动可以绝无仅有能够
按照自己意愿创造世界正是编程魅力所在 2 )。
正如现实世界物理定律支撑一样编程创造世界程序员输入代码
构筑规则支撑通过创造 Ruby 这样编程语言尤其感触不过
4
----------------------- Page 19-----------------------
1.1 
编程本质
便只是编写小的程序本质
相同可以按照自己意愿创造世界
因此正是因为具有创造性这样
特质编程吸引包括在内
无数程序员投入其中一发不可收拾
将来如果能够星际迷航
世界那样只要通过计算机讲话
获取所有信息那么编程也许
没有那么必要
其实搜索引擎出现之后类似 2 编程乐趣在于创造性
状况已经正在上演孩子们
他们经常频繁电脑跟前从来没有进行编程他们电脑只是
获取信息渠道或者朋友交流媒介而已编程这种爸爸一种
复杂”,他们觉得自己没什么关系
不过通过编程自由操作计算机创造自己世界这样乐趣如果他们了解的话
觉得遗憾这样乐趣不是通过强加方式能够受到而且强制方式
可能反而他们心里厌恶种子感到进退两难教育孩子真是容易
编程具有创造性同时艺术一面摄影出现之后绘画已经基本上丧失用于
记录功能 即便如此颇具艺术绘画作品还是层出不穷将来即便编程必要性
消失可能还是为了艺术乐趣继续编程其实星际迷航中的世界那样
计算机打开Debian GNU/Linux 8.0 模拟器程序”,这样世界
意思不是吗
快速提高性能改变社会
我们视角计算机业界决定方向性问题重要定律”,其中重要

莫过于摩尔定律摩尔定律美国英特尔公司创始人之一 ·摩尔 40 多年
1965 发表论文提出这个定律内容如下
· ·E 摩尔(Gordon Earle Moore ,1929— ),1968 参与创立英特尔公司此后担任英特尔公司总裁
首席执行官董事长职位退休
5
----------------------- Page 20-----------------------
1 编程时间空间
LSI
中的晶体管数量 18 增加① 。
LSI
集成 18 意味着 3 可以达到原来 4 ,6 可以达到
16
指数增长因此,30 我们可以达到原来 100 。LSI
基本上 CPU 性能内存容量直接相关 40 年中计算机性能就是
关系飞速增长此外集成可以影响价格因此性能对应价格反过来指数
下降
想想看现在附近电子商店售价 10 日元左右约合人民币8000 笔记本电脑
性能恐怕已经超过 20 多年超级计算机 3 )。况且超级计算机租金就要
差不多 1 亿日元约合人民币 800 ),已经这么如果买下的话多少
……
20
年前超级计算机 最近笔记本电脑
处理速度相同
3 20 年前超级计算机现在笔记本电脑性能处于同一水平
大学毕业之后就职一家公司索尼生产 Unix 工作站配置大概

‰ CPU :
摩托罗拉 68020 25MHz
‰
操作系统:NEWS-OS 3.3a (基于4.3BSD )
‰
内存:8MB
‰
硬盘:240MB
‰
价格定价 155 日元约合人民币 12
① LSI
大规模集成电路”。摩尔定律源于摩尔 1965 4 19 发表电子学杂志 114
集成电路填满组件文章原文中的说法每年增加”。 1975 摩尔一定
修改增加”。摩尔公开表示现在普遍流行 18 增加说法并非本人
提出而是出自英特尔公司名叫 David House 同事
6
----------------------- Page 21-----------------------
1.1 
编程本质
现在几乎不相信索尼曾经生产 UNIX 工作站以至于工作站”(Workstation )
本身已经几乎淘汰工作站曾经那些工程使用性能一般个人电脑
一些计算机大多数情况安装 UNIX 操作系统)。
工作站曾经最初开始编写 Ruby 使用机器现在自己家里计算机已经
Core2 duo 2.4GHz CPU 4GB 320GB ① CPU
内存 硬盘单纯比较一下的话频率大约
工作站 100 内存容量大约 500 硬盘容量大约 1300 计算机发售
时间大约 18 按照摩尔定律计算集成增加应该 64 可见内存硬盘
增加速度已经远远超过摩尔定律规定速率
当时网络电子邮件网络新闻组主流网络通信还是电话线路上通过调制
解调(Modem )进行回头当时杂志看到“9800bit/s 超高速调制解调器售价
198 000
日元约合人民币 1.6 )”这样广告还是感到震惊最近我们已经见到
模拟方式调制解调器最后调制解调器速度 56Kbit/s ,售价大约日元
正是摩尔定律力量这个业界各个领域中都经历飞跃成长半个世纪以来
计算机相关所有部件随着时间变得性能容量价格便宜
摩尔定律影响我们社会发生翻天覆地变化计算机现在已经变得随处可见
应该摩尔定律会所带来变化
现在手机 iPhone ,这个东西与其手机不如拥有通信功能迷你
计算机作为玩具实在有趣因为整天鼓捣还是家里差评这样东西
日元不得不感叹文明进步差不多同样时间女儿
普通手机手机 iPhone 不一样只是那种一般多功能③ ,仔细这种
手机上网装有 Web 浏览器电子邮件日程表软件不错计算机
当初感到惊奇这个手机居然安装 Java 虚拟机这样一来说不定运行
JRuby
光是日本全世界现在拥有这样便携式计算机通过无线网络联系
在一起这样情景 20 年前简直想象因此可以计算机大规模普及甚至
整个社会形态
2009 当时配置,2012 现在配置 Core i7 2.7GHz (双核线程),8GB 内存、300GB 固态硬盘
(SSD )。
经过3 时间好像没有进步。(
网络新闻组这个现在已经淘汰简单一种类似分布式网络讨论东西。(
原文“ガラケー”,“ガラパゴス ・ケータイ”简称日本有的说法。“ガラパゴス”
加拉帕戈斯群岛位于太平洋东部由于与世隔绝生态系统十分独特栖息巨蜥其他地方
没有奇特动物这个暗示智能手机快速普及今天日本市场存在仿佛与世隔绝主流
技术格格不入功能服务以及带有这些功能服务手机算是日本手机市场特色
7
----------------------- Page 22-----------------------
1 编程时间空间
以不变应万变
摩尔定律引发计算机方面变化可以翻天覆地形容并不所有一切
发生变化 4 )。
算法 算法
1980
2012
4 计算机不断进化法则保持不变
① 300
比如说算法以不变应万变称为古老算法辗转除法公元前
提出此外计算机科学中的大多数基本算法 20 世纪 60 年代提出
我们想想看电子邮件情形。15 年前几乎没什么使用电子邮件现在电子
大家身边如影随形工具甚至不少一天到晚拿手收发邮件邮件影响
生活甚至改变我们生活方式
然而邮件基础技术却是出人意料古老世界第一电子邮件 1971 发送
现在包括手机邮件在内遵循 RFC822 规范 1982 制定差不多距今 30 多年
东西此外现在依然作为主流广泛使用 TCP/IP 互联网通信协议差不多
时候制定
也就是说这个技术本身以前存在只是一般人知道而已重要
人类自身变化没有那么圣经之类古典著作惊奇发现
人类几千年前到现在纠结那些事情几乎没什么变化人类本质技术进步
辗转除法又称欧几里得算法欧几里得几何原本记载一种自然数最大公约数算法
目前为止已知古老算法现代算法概念一种递归算法
8
----------------------- Page 23-----------------------
1.1 
编程本质
不过细枝末节改变罢了
摩尔律所带来变化不是改变人类自身以及计算本质而是以往非常昂贵
计算机以及只有特殊部门需要东西普及老百姓这个侧面来讲
带来变化的确十分巨大
摩尔定律局限
无论如何 40 摩尔定律的确一直改变世界但是这个定律真的完美
指数增长趋势如此时期能够一直成立本身不自然实际上这个
无敌摩尔定律最近仿佛开始显露出一些破绽我们可以预料将来一定
出现一些因素摩尔定律继续生效构成障碍
首先物理定律局限。LSI 现实世界物理存在东西自然受到物理定律制约
40 ,LSI 一直不断变得更加精密甚至快要到达量子力学管辖地盘① 。 LSI
精密化达到这种程度日常生活一些从来不必在意小事都会变成十分严重问题
第一重要问题光速光速 30 万千 1 秒钟可以地球 7 这个
十分有名小孩子知道不过正是因为光速实在日常生活我们往往可以认为
光速无穷
然而,CPU 时钟频率已经到达 GHz 尺度比如说 3GHz 频率波形
1 时钟周期时间只能前进 10cm 距离
而且最近 LSI 电路宽度已经缩小只有纳米(nm ), 1nm 等于 100 分之
mm ,非常小的尺度 1nm 长度只能排列几个原子因此这样原子尺度
上来制造电路相当困难
LSI ②
上去这样细微尺度波长甚至
中的电路采用一种印刷技术
问题因为如果图像尺寸波长无法清晰可见光波长范围
400 ~800nm ,
因此最近 45nm 制程 LSI 无法可见光制造
经典力学经典动力学用来描述宏观系统集成电路精密程度达到原子级别微观系统经典
理学显得力不从心这就必须依靠用来描述微观物质量子力学
这里光刻”(Photolithography )。
9
----------------------- Page 24-----------------------
1 编程时间空间
这种原子尺度电路保持绝缘相当困难简单就是电流通过原本
通过地方称为电流电流不但浪费电力某些情况降低 LSI 性能
电流引发其他问题比如发热随着 LSI 越来越精密密度越来越
随之提高现在 CPU 这样高密度 LSI ,密度已经熨斗或者烧烤差不多
因此必须风扇装置持续进行降温这个趋势发展下去密度早晚媲美火箭喷气
如果没有充分散热措施 LSI 本身都会熔化
由于电流密度问题最近几年,CPU 性能提高似乎到了瓶颈大家可能
注意到了几年电脑配备 3GHz、4GHz CPU ,最近主流电脑配置
清一色 2GHz 上下造成这个现象原因之一就是上面提到那些问题使得 CPU 一味
频率时代到了尽头此外现在 CPU 性能对于运行 Web 浏览器收发邮件日常
已经足够原因
上面这些大家可能感到称霸 40 多年摩尔定律快要不行了不过英特尔
依然主张摩尔定律至少维持 10 ”。实际上人们可以使用特殊材料制造 LSI ,
以及使用 X 代替可见光进行光刻通过这些技术手段摩尔定律应该
一阵子
此外由于通过提高单一 CPU 密度实现性能提升已经非常困难因此 LSI
集成多个 CPU 方法逐渐成为主流英特尔公司 Core2 i5、i7 这样 LSI 集成 2 ~
8
CPU 核心”(Multi-core )CPU ,目前已经普通电脑反映上面
提到趋势
比起拥有复杂电路设计 CPU 内存部件由于结构简单平均因此工艺精密
更加容易今后时间,CPU 本身性能提升已经十分有限 CPU 内存容量
增大硬盘半导体 SSD 转变成为主流
社会变化编程
前面我们讨论摩尔定律带来变化以及今后趋势简单预测多亏摩尔定律
我们现在可以大量高性能价格计算机产品那么这种变化编程产生怎样

接触编程 20 世纪 80 年代初那个时候使用电脑目的就是编写 BASIC 程序
无论性能还是容量那个时候计算机非常差劲根本无法现在计算机相提并论
10
----------------------- Page 25-----------------------
1.1 
编程本质
必须使用 BASIC 这种十分差劲编程语言这种环境对于编程制约相当
编写许多现在看起来不起眼游戏差劲 BASIC 计算机性能感到十分不爽
一边立志总有一定要正经计算机”,一边搜集书本杂志中的信息自己
”。
另一方面现在计算机已经随处可见着手这样个人计算设备不在少数
孩子们就读学校设有理科教室音乐教室并列电脑教室有时计算
进行授课这样时代中的年轻人他们对于编程怎么
由于职业关系家里计算机不怎么经常可以计算机数量
家里数量① ,当然要是上手之类的话便是生活这样充满
计算机家庭孩子们对于编程貌似没有什么兴趣
那么他们计算机什么事比如邮件博客朋友交流维基百科查阅
需要信息还有 YouTube 看看动画片之类
Dolittle ②
初中学校曾经组织一种叫做 “ ”编程语言 实习孩子们好像
感兴趣不过没有进一步发展真正编程对于他们网站看看 YouTube 、
邮件有时候网购在线竞拍这些已经足够
学生时代朋友现在正在大学任教现在信息技术专业不但不如
热门而且进来学生编程经验比例下降似乎意味着计算机普及
但是编程普及一点没有提高真是令人不已
猜想大概由于随着软件发展不用编程可以计算机因此学习编程动力
没有那么此外现在大家认为软件开发非常辛苦工作可能导致
信息技术专业人气下滑原因
话虽如此并不说真的一点希望没有几年来叫做“U20 Pro Con”
20 以下青少年对象编程大赛担任评委每年参赛作品总能见到一些水平
程序
也许 因为担任评委缘故每年看到自制编程语言方面参赛作品
十分震惊欣慰自己还是高中生时候虽然创造一种编程语言完全不知
怎样到头来毫无进展这个角度这些参赛年轻人能够完整设计实现
本行 4 孩子因此家里共有 6
② Dolittle
大阪电气通信大学开发一种教育编程语言主页:http://dolittle.eplang.jp/ 。
11
----------------------- Page 26-----------------------
1 编程时间空间
编程语言当年优秀因此他们将来发展充满期待
这个世界有一些即便培养他们拥有想要编程欲望这样虽然
小众他们通过互联网获取丰富知识并不攀登编程领域高峰编程领地不会
计算机普及那样飞速扩展水平最高水平往往变得越来越这样状况
我们希望看到还是希望看到没办法做出判断
现代社会已经离不开计算机驱动计算机软件这个角度希望
能够积极参与编程工作此外希望大家不仅仅软件开发作为工作
而是希望能够受到软件开发带来那种创造乐趣心潮澎湃感觉”。
12
----------------------- Page 27-----------------------
1.2
未来预测
没有个人能够真的看到未来也许正是因为如此人们想要预知未来预言
方式充满兴趣血型出生日期天干地支风水依据占卜非常热门事实上
杂志电视节目每次占卜内容
这些毫无科学依据占卜方式靠谱虽说如此占卜还是大肆流行起来其中
这样一些理由
首先理由莫过于效应”① 。“效应一种心理学现象
一些原本准的模棱两可一般性描述自己身上认为这些描述
准确比如一些受试者心理测试问卷无论受试者如何回答问卷问题
他们提供事先准备内容差不多的测试结果大多数受试者都会认为这个结果自己
描述非常准确觉得占卜”,其实多半效应导致即使随便说说
一些有人深信不疑说不定人类一种本能人类心理到底为什么拥有
这样性质
其次算命先生自称预言家实际上利用称为”(Cold
reading )
”(Hot reading )技巧人们相信他们不同寻常能力”。
就是通过观察对方言行举止中的一些细微进行揣测技巧夏洛克 ·
福尔摩斯委托人运用那种技巧差不多例如通过说话口音判断出生地通过
泥土判断对方之前什么地方等等中的代表没有事先准备
意思
相对通过事先对方进行详细调查准确说出对方情况逢场作戏)。
通过事先调查掌握对方家庭构成目前遇到问题等等当然能够一语中的加上
拥有超能力一样有人深信不疑
实际上效应(Barnum effect )发现心理学家 ·(Bertram Forer ,1914—2000 ),因此
称为效应(P.T. Barnum )著名美国马戏业者之所以名字命名因为曾经
,“表演之所以受欢迎因为其中包含每个人喜欢成分”,因此分钟都会有人上当受骗”。
13
----------------------- Page 28-----------------------
1 编程时间空间
结论占卜之类方法靠谱它们科学那么有没有科学一点方法能够

预测未来比如说艾萨克 ·阿西莫夫基地系列描写心理史学那样
科学未来预测
心理史学阿西莫夫创造虚构学科气体分子运动类比我们虽然无法确定
气体分子运动方式对于无数气体分子组成气体我们可以计算整体
方式同样对于无数组成集团行为可以通过数学方法进行预测
这样一类的话是不是感到说服力
基地系列是以基于心理史学未来预测描写整个银河系舞台人类
千年历史
然而现实特定个人行动往往能够大幅左右历史走向便是整体
数学公式描述人类行为还是太过复杂心理史学也许只能停留幻想而已虽然心理
史学只是完全虚构学科并不意味着不可能通过科学方法预测未来虽然我们
无法未来作出完全准确预测限定条件还是可以一定概率未来作出预测
尤其是我们预测不是未来人类行动而是纯粹预测技术发展情况因此,IT 领域
可以比较容易通过上述方式进行未来预测领域
IT
未来预测
之所以 IT 领域未来比较容易预测理由计算机出现到现在已经
半个世纪 40 多年时间计算机基本架构没有发生变化现在主流 CPU
架构英特尔 x86 架构基础可以追溯 1974 问世 8080 ,其他计算机架构
根本部分大同小异意味着计算机进步方向不会什么变化我们理由预测
未来应该位于过去到现在这个方向延长线上 1 )。
如果量子计算机这样现在计算机架构完全不同东西成为主流的话我们预测
成立不过还好短时间比如 5 之类这样技术应该无法实现此外
艾萨克 ·阿西莫夫(Isaac Asimov ,1920— 1992 )著名美国科幻小说作品提出现代科幻
小说甚至现代科学技术影响深远概念著名机器人定律”。基地系列(The Foundation Series )
西撰写系列科幻小说作品基地系列作品包括基地三部曲》、《基地基地前传大部分
14
----------------------- Page 29-----------------------
1.2 
未来预测
这个行业,5 、 10 以后未来已经算是相当遥远即便预测没有什么意义
目前这样趋势还是问题不大
未来
现在
过去
1 过去未来发展方向
支配计算机世界过去未来变化方向代表性理论就是 1-1 已经讲解
定律”。
LSI
中的晶体管数量 18 增加
摩尔定律影响电路变得更加精密,LSI 成本不断降低性能不断提高结果
过去 40 年中
‰
价格下降
‰
性能提高
‰
容量增大
‰
带宽增加
这些指数关系发展指数关系
一样增大速度十分
惊人 2 )。
指数关系发展趋势预计今后
保持差不多的速度就是 IT 未来预测基础性能
另外需要考虑问题就是不同领域各自
发展速度。IT 相关各种数值的确指数
增加大家步调并不完全一致例如
相比 CPU 处理速度提高存储器容量
加速度上面两者相比数据传输速度 1980 1990 2000 2010
增加显得跟不上这种发展不平衡左右 2 性能指数关系增加
我们未来
15
----------------------- Page 30-----------------------
1 编程时间空间
极限未来预测
下面我们介绍一种预测未来时候名叫极限思考简单技巧
提出极限编程”(eXtreme Programming,简称 XP )手法肯特 贝克著作·
极限编程这样写道:①
第一次构建 XP 到了控制旋钮旋钮一种经验

告诉有效实践所有旋钮 10,然后出现什么情况
我们可以同样方法未来作出预测比如说,“如果计算机价格越来越便宜
便宜极致时候怎么样?”“如果我们能够高性能计算机怎么样?”“
计算机存储容量增大想象程度怎么样?”“如果网络带宽变得非常的话
?”
大家怎么认为
价格未来
首先我们看看价格如果今后计算机价格不断下降意味着什么
第一普通人所能拥有计算机性能现在大大提高第二现在没有使
计算机地方以后都会安装计算机
这里有意思现象根据摩尔定律关于计算机指标发生剧烈变化
PC 价格似乎变化没有那么。1979 发售 NEC PC-8001 定价 16 8000 日元
人民币 1.3 ),现在主力 PC 价格差不多 10 日元约合人民币 8000 上下
即便考虑物价变化因素还是出人意料稳定可能意味着人类对于 PC 购买力
差不多只有这个程度不断提高性能价格之间寻求一种平衡因此估计普及计算
价格今后可能大幅下降关于将来 PC (PC 计算机样子我们性能
进行讨论
肯特贝克(Kent Beck ,1961— ),美国著名软件工程师极限编程思想创始人之一。《解析极限
编程——拥抱变化》(Extreme Programming Explained: Embrace Change ),第一出版 1999 2005
出版第二
译文译本解析极限编程——拥抱变化》,人民邮电出版社(2002 )。
16
----------------------- Page 31-----------------------
1.2 
未来预测
关于目前尚未开发领域安装计算机其实现在已经上演例如以前
电子电路构成电视机现在安装 CPU、内存硬盘部件硬件 PC
什么两样并且安装 Linux 这样 操作系统此外以前单片机实现部分现在
开始带有操作系统计算机”,这样嵌入系统软件比例越来越
可以外观长得计算机计算机越来越这样计算机进行软件开发重要性
越来越例如现在由于内存容量 CPU 性能限制无法实现开发工具语言
嵌入软件开发逐渐成为可能
性能未来
10 计算机性能变化趋势,CPU 自身性能提高似乎已经快要到达极限
几年感觉到 PC 时钟频率似乎到了 2GHz 再也这种性能提高停滞
耗电 电流密度诸多原因导致因此单一 CPU 角度恐怕无法
继续过去那样指数增长势头
那么这样下去结果怎样推测未来计算机性能最好办法看看现在超级
因为超级计算机为了实现高性能采用那些技术其中一部分根据摩尔定律
越来越便宜 5 10 将来这些技术主流 PC
那么作为现在超级计算机代表 1 超级计算机指标
看看 2012 目前世界超级计算机 性能 10000TFLOPS
价格 1120亿日元约合人民币 90 亿
性能数据 1 )。虽然性能
CPU
数量 88128
天文数字 20 这种程度 核心数量 705024
内存 2.8PB (平均CPU 拥有 32GB )
性能可能只能算是一般般
说不定不久将来,1024 笔记本电脑已经一般配置如果服务器环境的话
也许现在超级计算机这样数万 CPU、十万核心配置已经非常普遍难以置信
这样环境编程变成什么样子为了充分利用这么 CPU ,软件及其开发
环境如何进化
(K computer )设在日本化学研究所超级计算机2005 开始研发,2012 6 正式宣告完成
名称表示运算速度可以达到 1 ( 10000 万亿 1016 级别根据世界超级计算
TOP500 排行榜数据出版当时(2012 4 ),“依然保持世界超级计算机纪录
2012 6 纪录美国 IBM 超级计算机”(Sequoia )打破
17
----------------------- Page 32-----------------------
1 编程时间空间
考虑这样环境认为未来编程语言之间应该如何充分利用 CPU 资源这个
方面进行争夺便是现在已经多语言提供并行处理功能而今并行处理
变得愈发重要如果多个核心性能充分利用起来说不定单独核心性能变得
那么重要
容量未来
存储器容量内存容量硬盘容量增长速度指标。2012
一般 笔记本电脑配备 4GB 内存 500GB 左右硬盘加上外置硬盘的话购买
2 ~3TB
存储容量不会普通人拥有存储容量达到 TB
10
年前还是想象事情仅仅多少时间我们可以电子商店轻松 TB
容量硬盘
那么存储器容量增加将来带来哪些变化大家都会想到一点到底
才能那么数据填满如此巨大容量
实际上一点根本用不着担心我们来回一下无论存储容量变得多大不知怎么
好像多久为了配合不断增加存储容量图片数据视频数据变得更加精细
尺寸变得另外软件变得越来越臃肿占用内存越来越以前软件到底
怎样那么小的内存运行如此流畅真是想不通
因此问题我们如何利用这些数据也许面向个人数据仓库之类数据分析工具
开始受到关注当然这种工具到底应该客户端运行还是服务器运行取决于性能
带宽之间平衡
存储器容量方面未来预测相关值得关注动向就是访问速度虽然容量
惊人速度增长读取数据速度没有按照匹配速度提高硬盘寻址速度没什么
长进总线传输速度半斤八两不过闪存这样硬盘外部存储设备现在
已经变得越来越便宜 闪存构成固态硬盘(Solid State Drive ,SSD )已经相当普遍
可以作为硬盘替代品按照这个趋势发展下去不久将来说不定超高速容量
核心内置缓存高速断电丢失数据内存(RAM ),以及低速永久保存数据外部
存储器(HDD )构成层次结构将会消失取而代之可能将会大规模缓存以及
永久保存数据内存构成层次结构如果高速内存能够永久保存数据
过去结构数据库系统产生大规模结构改革实际上高速 SSD 前提数据库
系统目前已经进行研发
18
----------------------- Page 33-----------------------
1.2 
未来预测
带宽未来
带宽也就是网络数据传输速度不断增大一般家庭上网速度已经模拟
调制解调器时代不到 100Kbit/s,发展 ADSL 时代 10Mbit/s,到现在光纤时代超过
100Mbit/s,
最近理论超过 1Gbit/s 上网服务开始面向一般家庭推出
网络带宽增加网络端的平衡产生影响网络速度时代各种处理
本地进行然后处理结果分批发给中央服务器中央服务器进行统计这样
十分常见这就好像回到计算机没有普及大家算盘账本本地处理时代
然而后来各种业务处理中都开始使用计算机每个人数据可以发送中央
进行实时处理由于那时计算机非常昂贵因此只是周围布置一些称为
机器实际处理还是设在中央大型计算机完成那是中央集权时代
以后随着计算机价格下降每个人可以拥有自己计算机由于计算机
完成工作因此每个人客户端计算机可以完成一定程度处理
然后仅仅最终结果传送位于中央服务器”,这样系统结构开始普及起来也就是所谓
客户端 / 服务器系统”(Client-Server system ),有人称为“CS 系统”。
然而如果提高的话服务器完成处理系统构成更加容易
例子就是万维网(World Wide Web ,WWW )。缓慢年代为了查询数据直接
访问可能位于地球背面服务器这种难以想象如此浪费贵重带宽资源
狗血淋头话说现在网络带宽已经白菜一样便宜这样一来客户端
需要准备浏览器这样通用终端可以使用全世界各种服务如此美好世界
已经为了现实由于大部分处理服务器执行因此乍看之下仿佛中央集权时代
复辟不同现在我们可以使用服务多种多样而且它们位于全世界各个角落
但是计算机性能带宽之间平衡引发拔河比赛没有到此结束近年来为了
丰富服务倾向于 JavaScript 浏览器运行实际上客户端 / 服务器系统
马甲复活此外服务器计算机变成许多计算机紧密连接
云计算系统角度的话以前大型提供服务现在变成客户
/ 服务器结构提供
今后性能带宽寻求平衡过程网络彼此端的系统构成钟摆一样
以往情况随着每次钟摆来回系统规模扩展自由度能够得到提高
今后发展一定遵循这样趋势
19
----------------------- Page 34-----------------------
1 编程时间空间
小结
这里我们瞄准过去到现在发展方向延长线运用极限思考尝试未来进行
预测书籍可以存放,5 、 10 之后再次翻开时候到底这里预测
不能言中言中的话自然感到开心言中的话我们一笑了之胜败兵家常事
20
----------------------- Page 35-----------------------
1.2 
未来预测
编程语言
过去现在未来 2
21
----------------------- Page 36-----------------------
2.1
编程语言世界
大家知道世界编程语言什么一般认为 1954
开始开发 FORTRAN 语言
然而仔细想想看到底什么编程语言如果机器
控制看成编写程序的话那么编程起源便可以追溯
织机上面使用打孔纸带 1 )。
1801
正值工业革命期间织机发明使得提花
图案可以通过程序自动完成从前各个家庭
自动纺织用于家庭作坊自动纺织生产
相当于这些家庭纺织放大那些自动纺织
打孔纸带
可以通过类似打孔纸带东西输入图案当然最近
1 织机示意图
恐怕没有亲眼纺织
这种打孔带来控制机器想法各个领域产生影响例如英国从事通用计算

研发查尔斯 ·自制分析打孔带来输入控制程序遗憾
由于资金其他一些问题生前未能分析制造出来
不过分析设计已经完成用于分析程序作为文档保留下来协助开发这些

程序英国诗人拜伦 ·据说师兄关系如果不算分析
设计者那么世界第一程序员实际上女性为了纪念还有一种编程
语言名字 Ada 命名③ 。
点题现在编程女性人数一点有目共睹尤其是开源相关
活动男女比例达到 100 1 稀奇其实计算机早期时代记录表明人们大都
查尔斯(Charles Babbage ,1791— 1871 ),英国数学家计算机先驱可编程计算机发明者分析
(Analytical Engine )设计一种机械十进制通用计算机虽然由于种种原因真正制造
出来设计理念可以 100 电子通用计算机先驱
(Ada Lovelace ,1815— 1852 ),原名奥古斯塔拜伦丈夫威廉
姓氏
这里 1980 美国军方设计一种名为 Ada 编程语言
23
----------------------- Page 37-----------------------
2 编程语言过去现在未来
认为程序员应该女性从事工作也许人们程序员当时电话交换机接线员业者
女性居多看成同一类型工作
称为世界第一计算机 ENIAC ① (1946 程序不是打孔纸带而是通过
电线方式输入觉得一种倒退
不过无论电线还是打孔纸带不大可能实现复杂程序真正程序恐怕
等到存储程序计算机出现之后一般认为世界第一存储程序电子计算机 1949
出现 EDSAC ② 。
到了这个时候所谓机器语言就算正式问世当时计算机程序机器语言
编写那个时候不要编译器汇编发明出来因此使用机器语言
当然
说到底机器语言就是数字计算步骤指令查出对应机器语言编码
人工数列这个工作可不容易或者以前虽然没有意识我们现代人
这种辛苦简直难以置信比如说引导程序机器语言数列整个下来每次
时候手工输入进去机器语言指令全部下来不用草稿直接输入机器
语言指令正确运行——“古代程序员留下无数光辉事迹或者传说),那时候
人们真是伟大
然而一个人忽然想到这种工作本来应该计算机擅长那么计算
自己好了于是人们更加容易记忆指令助记符代替数值开发
一种能够自动生成机器语言程序就是汇编
汇编用来解释汇编语言程序汇编言中使用助记符计算机指令
一一对应关系早期计算机主要还是用于数值计算因此数学主宰数学世界
数百年来传承下来语言就是因此接近形式编写计算机指令显得相当
方便随后,FORTRAN 1954 问世。FORTRAN 这个名字意思
FORmula TRANslator
 翻译
也就是说编程语言编程根据自己需要发明出来早期计算机由于性能不足
① ENIAC (Electronic Numerical Integrator And Computer ,
电子数值积分计算机),美国宾夕法尼亚大学
工程学院设计建造电子计算机 1946 公布美国陆军弹道研究实验室服务
② EDSAC (Electronic Delay Storage Automatic Calculator ,
电子延迟存储自动计算器),英国剑桥大学数学实验
(Maurice Wilkes ,1913—2010 )教授及其团队 1946 开始设计建造电子计算机
1949
5 6 正式运行
24
----------------------- Page 38-----------------------
2.1 
编程语言世界
运算成本因此编写维护程序看成是非工作编程语言正是开始摆脱人性
象征
其实助记符自动生成机器语言汇编以及人类易懂语句生成机器
编译器当时认为革新技术称为自动编程”。此外编译器开发技术
甚至视为人工智能研究一部分
历史埋没先驱
一般大家认为 ENIAC 世界第一计算机 FORTRAN 世界第一编程语言
然而事实果真如此觉得必要刨根问底
实际上如果仔细查阅一下计算机历史还是发现一些不同观点
首先世界第一计算机其实应该 - 计算机”(Atanasoff-Berry
Computer ,
简称 ABC ),计算机测试完成 1939 ENIAC 而且,ABC
数值表现方法采用现在广泛使用二进制计算(ENIAC 十进制计算), ABC
其中先进
ENIAC
甚至不能世界第二计算机当时第二次世界大战美国敌对
开发用于土木工程计算计算机 Z3 ① 。计算机完成 1941 ABC 一样
数值表现采用二进制电子管组成 ABC ENIAC 不同,Z3 继电器计算机
遗憾,Z3 1944 柏林轰炸
那么编程语言方面如何通过查阅资料发现开发 Z3 工程师拉德 ·

1942 1945 年间开发一种名为 Plankalkül 编程语言 FORTRAN 将近 10
然而,Plankalkül 只是设计出来没有正式发表而且用于编程语言编译器没有
开发出来
Plankalkül
设计直到 1972 正式发表第一用于语言编译器正式实现
已经 1998 因此如果完整开发工作编程语言,FORTRAN 作为古老
语言地位还是无人能够撼动
Z3 之前当然开发 Z1 Z2 ,它们用于特定用途计算机并非通用计算机。(
拉德(Konrad Zuse ,1910— 1995 ),德国土木工程计算机先驱。Plankalkül 认为世界
第一高级编程语言 名称德语中的意思用于规划正式系统”(Formal system for
planning )。
25
----------------------- Page 39-----------------------
2 编程语言过去现在未来
Plankalkül
由于种种原因淹没
P1 max3 (V0[:8.0],V1[:8.0],V2[:8.0]) =>
因此后世编程语言 R0[:8.0]
max (V0[:8.0],V1[:8.0]) => Z1[:8.0]
没有产生影响但是考虑 max (Z1[:8.0],V2[:8.0]) => R0[:8.0]
语句子程序条件判断循环浮点 END
P2 max (V0[:8.0],V1[:8.0]) => R0[:8.0]
小数计算数组拥有层次结构结构、 V0[:8.0] => Z1[:8.0]
(Z1[:8.0] < V1[:8.0]) -> V1[:8.0] => Z1[:8.0]
断言异常处理目标搜寻功能
Z1[:8.0] => R0[:8.0]
一些甚至 10 出现 FORTRAN END
具备可见先进着实令人惊叹 2  Plankalkül 编写程序
2 Plankalkül 程序其中定义用于参数进行比较子程序 max ,
利用这个子程序进而参数进行比较子程序 max3。其中所有运算过程表示
=> 结果保存位置这样形式相当有意思
编程语言历史
FORTRAN 之后,20 世纪 50 年代 80 年代初各种各样编程语言雨后春笋
出现从而成就编程语言研究飞速发展鼎盛时期
这里我们暂且抛开那些现在主流编程语言而是目光一些最近不怎么
听说值得品味编程语言借此简单介绍一下编程语言历史
1. FORTRAN
话说回来开始我们还是来讲 FORTRAN 这个主流语言
FORTRAN
作为实质上世界一种编程语言数值计算领域建立霸主地位需要
计算功能物理学家研究人员并不关心编程他们计算速度是否充分
利用过去成果重要
结果,FORTRAN 非常重视过去程序之间兼容性为此保留一些现在
已经几乎不再使用编码风格其中典型例子就是 FORTRAN 语法包含
这个难以置信规则
程序空格没有意义
这个规则意思空格只是为了易读加上编译器编译程序空格全部
去掉这样语法现代编程语言习惯简直匪夷所思为什么这样例如
26
----------------------- Page 40-----------------------
2.1 
编程语言世界
DO 100 I = 1,10
这样一行程序语法 DO 语句语法一致程序意思循环中将 1 10
依次赋值变量 I ,循环结束之后跳转行号 100 语句这里看起来没有什么
问题
不过如果 DO 语句“ 1,10”中的逗号句点的话发生什么事也就是说
DO 100 I = 1.10
我们之前已经,FORTRAN 忽略所有空格那么这个语句转换
DO100I=1.10
这样一来由于这个语句符合 DO 语句语法因此编译器这个语句理解
小数 1.10 赋值变量 DO100I”,编号 100 一行彻底忽略也就是说本来
意图执行循环现在变成赋值语句循环无效可以
分析相关研究相对落后年代设计出来编程语言有的悲剧
另一方面追求计算速度人们依然使用 FORTRAN ,并且积累一些十分大胆
技术例如为了限度利用超级计算机装备向量处理器自己改写 DO 循环
向量
最近超级计算机几乎不再使用向量结构因此遇到必须使用 FORTRAN
情况使用超级计算机研究人员开始使用 Java C++ 之类 语言不过即便如此
FORTRAN
没有消亡迹象
此外,FORTRAN 本身不断进化最新版本实现编程抽象数据结构
功能其他先进编程语言相比毫不逊色
2. COBOL
COBOL
这个名称 COmmon Business Orientd Language (面向商业通用语言缩写
对于面向科学计算 FORTRAN ,COBOL 作为面向商务计算出现编程语言 1959
开发出来
基于下面推理
‰
由于面向商业计算因此需要
‰
对于从事商业活动一般大众采用接近日常表达方式英语语法
采用数学容易理解
27
----------------------- Page 41-----------------------
2 编程语言过去现在未来
COBOL
采用类似英语语法对于这个日本人
AB = A B*

MULTIPLY A BY B GIVING AB.
真的容易理解恐怕大大问号
不过由于积累大量过去数据,COBOL 直到 21 世纪今天依然虽然计算机
杂志已经不到关于 COBOL 文章说法,COBOL 依然现在使用广泛
编程语言
实际上供职网络应用通信研究所一半技术人员 Ruby 进行软件开发
剩下一半使用 COBOL 。不过我们硬件只是普通 PC 服务器操作系统 Linux ,
其他事务监视器工具开源软件倒是少见
那么,COBOL 到底好不好这样没用 COBOL 角度一定
别的程序差别”,不过 COBOL 技术人员主张如果实现
簿数字右边移到左边即便现在 COBOL 效率最高”。即使排除他们已经
COBOL
这个因素这个评价还是相当值得回味
COBOL
不断进化据说最新版本增加面向对象功能
3. Lisp
Lisp
FORTRAN 、COBOL 一起称为古代编程语言巨头”(命名),名字

LISt Processor
处理器
Lisp
一种特殊编程语言因为原本不是作为编程语言而是作为一种数学计算

设计。Lisp 设计者约翰 ·设计一种 Lambda 演算基础图灵完全
约翰(John McCarthy ,1927—2011 )美国著名计算机科学家 1971 人工智能方面贡献
图灵奖。Lambda 演算(Lambda calculus )(Alonzo Church ,1903— 1995 )提出一种十分
简洁计算模型 Lisp 函数编程语言理论基础不同通用灵机计算模式图灵完全
图灵完全(Turing complete )一种计算模型拥有通用灵机相同计算能力一种计算模型可以实现
目前已知所有算法描述目前大部分通用编程语言 C、Java 图灵完全
28
----------------------- Page 42-----------------------
2.1 
编程语言世界
计算模型 Lisp (起源为了描述模型设计出来
不过没有想到作为一种计算机语言使用直到实验室研究生
·罗素 IBM 704 机器语言实现描述计算模型万能函数 eval (求值函数),Lisp 作为
一种编程语言正式诞生。①
此外,Lisp 早期能够数值以外进行处理语言对于 FORTRAN COBOL
只能进行数值计算语言,Lisp 擅长结构这样数值处理因此,Lisp 广泛
用于人工智能这样领域
20 世纪 60 年代一般人不会为了计算以外目的使用计算机因此 Lisp 主要大学
研究所地方传播
现在计算机字符串处理通信数值处理已经成为主流 Lisp 这样时代
先驱者此外 Java 广为人垃圾回收异常处理机制实际上最初 Lisp 发明
因此,Lisp 现在计算机科学基础构筑做出极大贡献尽管使用者寥寥现在
依然用于实用系统开发现役编程语言
4. SNOBOL
早期 Lisp 能够处理数据只有符号并不擅长处理字符串一种专门用于
字符串处理语言历史上扮演重要角色就是 SNOBOL (发音近似Snow Ball )。
SNOBOL
名字意思
StriNg Oriented symBOlic Language
字符串 面向 符号 语言
其中 N BO 位置好像奇怪不过在意就是。SNOBOL 革新在于是以
模板(Pattern )中心进行字符串处理可以现在AWK 、Perl Ruby 语言祖先
不过 SNOBOL 字符串模板不是正则表达式而是采用类似 BNF ( 范式)②
-
写法
这样编程语言积累经验 加快计算机数值计算以外方向发展
步伐
① IBM 704
IBM 公司 1954 推出内置浮点计算功能大型计算机,FORTRAN Lisp 编程语言
IBM 704 开发出来
- 范式(Backus-Naur Form ),约翰(John Backus ,1924—2007 )彼得(Peter
Naur ,1928— )
首先引入用来描述计算机语言语法符号一种描述上下文无关文法语言
29
----------------------- Page 43-----------------------
2 编程语言过去现在未来
3 展示 SNOBOL 模板匹配 N 皇后问题程序模板匹配跳转
明确标明标签这样风格现在已经不怎么见得到了
* N queens problem, a string oriented version to
* demonstrate the power of pattern matching.
* A numerically oriented version will run faster than this.
N = 5
NM1 = N - 1; NP1 = N + 1; NSZ = N NP1; &STLIMIT = 10*
9; &ANCHOR = 1**
DEFINE ('SOLVE (B)I')
* This pattern tests if the first queen attacks any of the othe
rs:
TEST = BREAK ('Q') 'Q' (ARBNO (LEN (N) '-' ) LEN (N) 'Q'
+ | ARBNO (LEN (NP1) '-' ) LEN (NP1) 'Q'
+ | ARBNO (LEN (NM1) '-' ) LEN (NM1) 'Q' )
P = LEN (NM1) . X LEN (1); L = 'Q' DUPL ('-',NM1) ' '
SOLVE () : (END)
SOLVE EQ (SIZE (B),NSZ) :S (PRINT)
* Add another row with a queen:
B = L B
LOOP I = LT (I,N) I + 1 :F (RETURN)
B TEST :S (NEXT)
SOLVE (B)
* Try queen in next square:
NEXT B P = '-' X : (LOOP)
PRINT SOLUTION = SOLUTION + 1
OUTPUT = 'Solution number ' SOLUTION ' is:'
PRTLOOP B LEN (NP1) . OUTPUT = :S (PRTLOOP)F (RETURN)
3  SNOBOL N 皇后问题程序
5.
数学语言
刚才已经,Lisp 数学诞生编程语言不过这样语言可不 Lisp
例如,Prolog 就是谓词逻辑基础编程语言。20 世纪 80 年代日本政府主导

第五计算机计划 采用 Prolog (准确应该是以Prolog 基础逻辑编程语言),
使得名声 第五计算机计划逐渐淡出人们视线之后,Prolog 随之销声匿迹
不过逻辑编程语言具备联合”(Unification )匹配模式最近备受关注函数
编程语言继承下来
① N
皇后问题(N-Queen Problem ),最初8 皇后问题即如何在国际象棋棋盘(8 ×8 )摆放8 皇后使得
它们彼此无法直接吃掉对方彼此不能处于同一直线线上)。8 皇后问题共有 12 独立旋转
对称作为同一的话),后来便由此引申 N 皇后问题此时皇后个数 N ,棋盘大小同时N ×N )。
第五计算机计划 日本通商产业经济产业主导国家开发计划目的开发高速
谓词逻辑推理并行逻辑计算机及其操作系统”。整个计划 1982 启动耗时 10 花费 570 亿日元
1992 宣布结束并称达到预期目标”。
30
----------------------- Page 44-----------------------
2.1 
编程语言世界
数学基础编程语言还有另外派系称为函数编程语言例如 ML 、
Haskell
属于一类一类数学语言比起机器处理方式倾向于直接表达表达
概念也就是说对于 How (如何实现),倾向于通过 What (想要什么表达问题
可以机器处理方式左右 问题抽象表达出来一种非常先进特性关于
一点后面未来编程语言进行详细介绍
6.
主流语言
众人那些编程语言必要特地这里介绍作为系统描述语言 C C++、
作为面向商务语言如日中天 Java ,以及 Web 领域十分热门 Ruby 、Perl 、Python 、
PHP
脚本语言属于主流语言
Java C++、
Smalltalk
Lisp 优点 Ruby 吸收过去语言优点基础加以独自发展形成语言
编程语言进化方向
过去编程语言历史我们可以看出编程语言不断试错过程发展起来
编程语言已经消亡仅仅历史留下它们名字其中包含思想后来
语言不同形式吸取借鉴
例如,SNOBOL 字符串处理功能可以现代脚本语言基本功能祖先此外,20
70 年代美国麻省理工学院(MIT )开发一种名为CLU ① 语言迭代(Iterator )概念
Ruby 代码(Block )形式继承下来
编程语言进化过程显著关键词就是抽象”。抽象就是提供抽象
概念使用者即便具备关于内部详细情况知识能够进行运用由于不必了解
内部情况因此称为黑箱”。
一些古老编程语言例如 BASIC 没有实现充分抽象虽然提供用于过程共享
子程序这个概念但是子程序只能通过编号调用而且不能传递参数由于赋予名称
抽象重要部分 抽象充分近代编程语言可以一系列
程序赋予相应名称
① CLU
MIT 芭芭拉(Barbara Liskov ,1939— )开发语言 2008 获得图灵奖

31
----------------------- Page 45-----------------------
2 编程语言过去现在未来
然而仅仅过程进行抽象远远不够几乎所有过程需要进行一定输入输出操作
不是数据无关因此下一个阶段数据进行黑箱显得非常重要刚才
提到 CLU ,就是数据抽象出现早期一种语言
数据抽象延长线上 自然而然产生面向对象编程概念所谓对象就是抽象
数据本身因此面向对象数据抽象之间仅仅现在 21 世纪编程
语言面向对象已经常识最近几乎所有语言或多或少提供面向对象能力

当然其中有一些语言故意提供对面对象支持
随着抽象不断深入程序员即便关心内部详细情况可以编写程序人类
一次所能掌握概念数量有限说法大部分一次只能驾驭 7 ±2 左右概念
一来如果能够问题处理方式更加抽象可以解决复杂问题
摩尔定律影响社会对于软件提出越来越要求人类社会越来越依赖计算机
因此需要开发可靠便宜软件

讲述软件开发名著神话作者弗雷德里克 ·布鲁克写道
无论使用什么编程语言生产基本语句需要几乎一定
也就是说如果描述同样算法,A 语言需要 1000 ,B 语言需要 10 的话只要
采用 B 语言生产效率可以提高 100
可能有人觉得”。比方 Java Ruby 描述同样算法语句
2 稀奇如果汇编语言 Ruby 相比的话也许产生 100 甚至 1000 差距
产生这样生产效率差异正是抽象力量抽象编程语言不必描述详细过程
从而可以简短代码达到目的抽象程度差异相比变量名称有没有指定数据类型
之类只能算是误差别的差异而已
例如中将介绍保罗格雷设计 Arc 语言至少其内功能支持面向对象
保罗有意这样设计。(
② 《
神话——软件项目管理》 (The Mythical Man-Month: Essays on Software Engineering )首次出版1975
1995 进行扩充再版弗雷德里克布鲁克(Frederick P. Brooks, Jr. ,1931— )美国软件工程师
主持开发 IBM OS/360 操作系统 1999 获得图灵奖
32
----------------------- Page 46-----------------------
2.1 
编程语言世界
未来编程语言
编程语言进化这个视角其实最近没有什么动作现在使用广泛编程
语言几乎 10 多年出现便是比较 Java Ruby 诞生 20 世纪 90 年代后半
距离现在已经 15 之前也许可以现在正是编程语言进化时机
最近受到 CPU 因素影响,Erlang ①这种并行处理语言到了不少关注不过
Erlang
早在 1987 诞生不是什么东西有点失望
那么未来编程语言究竟变成什么样

美国风险投资家、Lisp 启蒙作家保罗 ·格雷百年编程语言
想象 100 可能出现编程语言提议观点应用到现在编程语言
主张,100 编程语言进化主线应该少量公理基础拥有简洁
语言”。现有编程语言具有特征莫过于喜欢 Lisp 所以
主张实际上就是说,Lisp 100 编程语言进化方向
这样小人物叫板好像不自量力不过还是认为对于未来
基于过去到现在变化方向延长线上做出预测当然将来也许发生一些无法
预料状况从而大幅扭转之前前进方向不过这样事情定义本来就是无法预测
非要预测本质上毫无意义
作为编程语言御宅族通过反观过去半个世纪以来编程语言进化方向认为编程
语言绝对不会按照保罗 ·格雷向着干净方向进化现在编程语言
无论功能还是语法上都已经不是那样单纯虽然曾经有人努力尝试这些语言变得

简单包括保罗 ·格雷自己设计 Arc 在内不能算是成功尝试
在我看来编程语言进化动机不是工具语言本身简化而是通过这些工具
得到结果解决方案简洁表达出来半个世纪以来编程语言不断提供愈发
① Erlang
瑞典电信公司爱立信(Ericsson )旗下计算机科学研究开发一种编程语言发布 1987
1998 实现开源
保罗格雷(Paul Graham ,1964— )美国风险投资家计算机科学作家。《百年编程语言》(The
Hundred-Year Language )
收录保罗格雷文集黑客画家人民邮电出版社 2011 4
出版
③ Arc
语言 Lisp 方言之一保罗格雷罗伯特(Robert Tappan Morris ,1965— )
设计 2008 首次发布
33
----------------------- Page 47-----------------------
2 编程语言过去现在未来
抽象特性正是为了达到这个目的因此我们可以自然认为这种趋势将来
应该继续保持
基于上述观点如果预测 100 编程语言样子认为应该下面情况
其中之一
(1)
变化不大编程语言写法 20 世纪 80 年代开始几乎没有什么进化今后即便出现
写法只是现有写法变形而已。(发展上来比较悲观未来
(2)
使用编程语言编程这个行为本身存在人类可以通过计算机对话大概
语言查询处理信息。(类似星际迷航中的世界对于编程语言比较失落
未来
(3)
发明采用抽象写法编程语言这种语言现在想象不过应该现在
更加强调 What ,对于如何解决问题 How 部分细节不再需要人类过问。(难以预测
未来
当然上面预测只不过仅仅预测而已有可能未来实际情况大相径庭或者
实际大相径庭可能性比较不过话说回来, 100 已经不在这个世上这不
操心
20
编程语言
通过 100 预测我们明白预测 100 事情非常困难”。想想看, 100
年前飞机没有民用, 100 已经可以飞机舒舒服服稿子
足以说明想象社会变化相当困难
那么一点未来怎么样比如说 20 。20 年前日本刚刚年号平成① ,
现在那个时候相比印象社会应该没有发生非常极端变化计算机性能方面确实
长足进步不过发展趋势还是连续并非无法预测对于 20 未来应该可以
根据现在发展趋势做出判断
个人认为这么时间编程语言本身应该不会发生多大变化实际上现在使用
多语言 20 年前已经存在因此预计,20 语言应该分布处理
除了公元纪年日本人普遍习惯使用年号纪年中国封建王朝时期一样年号一般天皇更换
更迭。 1989 明仁天皇即位年号平成因此 2012 平成 24 平成之前上一个年号昭和
34
----------------------- Page 48-----------------------
2.1 
编程语言世界
计算机协作处理并行处理多个 CPU 协作处理功能进行强化使得开发者需要
花心思能够使用这些功能
之所以关注分布处理并行处理 因为今后个人可以通过云计算形式使用
计算机随着计算机 CPU 相当于安装 CPU ,这些情形
容易想象
不过认为现在线程、RPC (Remote Procedure Call ,远程过程调用使用
处理并行处理形式晚会遇到瓶颈核心数量超过时候指定变得
毫无意义调试起来变得非常痛苦期待 20 能够出现突破这种局限技术
无需操作可以实现分布处理并行处理
学生想象
几年曾经母校筑波大学开展一次关于编程语言集中讲座讲座学生
想象一下 20 编程语言这样题目讲座最后一天提交报告有意思
大多数学生没有做出上面关于分布处理并行处理之类技术性预测而是
提出 诸如编程变得简单语言”、“希望自然语言控制计算机之类想象通过
这些答案似乎可以看出他们平常为了完成编程作业折磨何等痛苦
不过这样答案也许蕴含真理近年来编程语言似乎越来越难以脱离 IDE
(Integrated Development Environment ,
集成开发环境单独出来对于 Ruby 有人
没有 IDE ?”之类问题当然消息最近 Eclipse NetBeans 已经支持 Ruby
有点跑题总之未来编程语言可能不会过去编程语言那样语言本身单独存在
而是编辑器调试性能分析器开发工具相互配合达到提高整体生产效率目的话说
就是 Smalltalk ①
历史是否重演
① Smalltalk
正是一种考虑编辑器调试性能分析器开发工具相互配合设计语言过去并不
算是成功随着技术进步理念获得越来越用武之地或许卷土重来可知。(
35
----------------------- Page 49-----------------------
2 编程语言过去现在未来
2.2 DSL(
特定领域语言
下面我们介绍一些值得关注编程语言功能首先我们 DSL (Domain Specific
Language ,
特定领域语言开始说起
所谓 DSL ,利用特定领域(Domain )专门设计词汇语法简化程序设计过程
提高生产效率技术同时编程领域专家直接描述逻辑成为可能。DSL 优点
可以直接使用对象领域中的概念集中描述想要做到什么”(What )部分不必
做到”(How )进行描述
例如名为 rake 编译工具 Ruby
task :default => [:test]
这个工具功能 make 差不多分析文件 task :test do
ruby "test/unittest.rb"
依赖关系 自动执行程序编译连接操作 end
描述依赖关系 Rakefile 就是使用 Ruby 语法一种 1 Rakefile 示例
DSL 。
1 就是 Rakefile 简单例子
这个例子表达下面意思
(1)
默认任务 test
(2) test
内容执行 test/unittest.rb
启动 rake 命令格式
rake
任务
如果这里任务省略的话执行 default 任务
Rakefile 对于依赖关系描述只是指定 task ,对于内部数据结构怎样以及
维持依赖关系如何实现等等具体问题无需涉及因为具体实现方式描述依赖关系
对象领域(Domain )无关
DSL
这个特定目的规模语言称呼最近出现比较叫法这种方法本身
不是什么稀罕东西尤其是 UNIX 社区诞生许多用来解释这样专用语言
工具
36
----------------------- Page 50-----------------------
2.2 DSL(
特定领域语言
其中行为单位进行文本处理脚本语言 awk 算是比较有名除此之外,UNIX
开发迷你语言”,比如用来描述依赖关系 Makefile 用来读取 make 命令
单位进行数据流编辑 sed 命令用来描述文档嵌入图像 pic 命令用来生成表格 tbl
命令等等此外为了这些迷你语言编写提供支持,UNIX 提供语法分析器生成
yacc ,以及词法分析器生成工具 lex ,yacc lex 本身拥有自己迷你语言
这些迷你语言几乎专用特定用途大多数情况无法完成复杂工作它们
能够简单配置文件描述内容命令处理带来灵活性因此 DSL
本质上相同
外部 DSL
这些迷你语言代表专用语言引擎实现 DSL ,称为外部 DSL 。UNIX
迷你语言文化 DSL 出现并非只有 UNIX 提供外部 DSL 。
Java 编写应用程序大量使用可扩展标记语言(XML )编写配置文件这种
XML
配置文件外部 DSL 一种形式此外数据库访问使用 SQL (Structured Query
Language ,
结构查询语言一种典型外部 DSL 。
外部 DSL 优点在于独立程序开发语言对于领域进行操作程序
一定一种语言编写例如用来对数进行操作程序有时可以 Ruby
开发有时可以 PHP 或者 Java 开发由于外部 DSL 独立程序开发语言因此
可以实现跨语言共享只要学会 SQL ,可以不同语言相同 SQL 进行
操作
正则表达式使用方法差不多正则表达式用来描述字符串模板一种外部 DSL ,
Ruby 、Perl 、PHP 、Python
多语言中都可以使用语法不同语言基本上通用① 。
这样好处不同语言中都可以使用字符串模板匹配通用功能
此外外部 DSL 实际上全新设计语言语言引擎因此可以根据目的进行自由
设计不必特定执行模块现有语言语法左右由于自由度特定领域能够
大幅度提高生产效率
也许大家认为设计实现一种语言非常辛苦工作如果规模不是的话实际上
实际上不同引擎正则表达式功能语法细微差别。(
37
----------------------- Page 51-----------------------
2 编程语言过去现在未来
没有那么名著《UNIX 编程环境》①为首许多书籍迷你语言制作作为例题
核心部分只要代码可以完成
然而高度自由设计双刃剑意味着程序员为了使用 DSL 必须学习全新
语言 SQL 这样作为数据库访问通用方式已经实现普及标准化语言还好如果为了
一种目的从零开始学习完全不同语言这样辛苦可不是
内部 DSL
外部 DSL 相对自然就是内部 DSL 外部 DSL UNIX 脱胎发展
DSL 源于 Lisp Smalltalk 文化之中
内部 DSL 不是创造一种语言而是现有语言实现 DSL ,作为 DSL 基础
现有语言称为宿主语言原理宿主语言可以任何编程语言不过还是适合
适合。Lisp、Smalltalk、Ruby 这些语言适合作为 DSL 宿主语言因此这些语言社区
经常使用内部 DSL 。
内部 DSL 优点缺点外部 DSL 正好相反也就是说内部 DSL 借宿宿主
言中借用宿主语言语法因此程序员无需学习一种语言理解内部 DSL 含义
宿主语言常识依然有效学习语言宿主语言知识成为不错航标
之前我们 rake 命令 Rakefile 这个例子就是一种内部 DSL 。 1 显示 Rakefile
代码用来 rake 命令描述编译规则 DSL 代码同时 Ruby 程序
内部 DSL 还有其他一些优点 DSL 编写复杂逻辑可以使用过程定义条件分支
循环作为通用语言宿主语言具备全部功能某种意义就是万能
此外,“寄生宿主语言上面意味着 DSL 实现相对容易一种迷你语言实现
再怎么简单宿主语言基础增加一些功能能够实现内部 DSL 相比只能甘拜下风
说到内部 DSL 缺点由于 DSL 语法限定宿主语言能够描述范围因此自由
比较不过自由度较好易读何况 Lisp 这样具备高性能功能语言
便是内部 DSL (一定易读代价可以实现相当自由度
个人观点易读实现容易内部 DSL 具备优势
① 《UNIX
编程环境》(The Unix Programming Environment ),Brain W. Kernighan Rob Pike , 1984 出版
作者来自贝尔实验室关于 Unix 操作系统早期著名著作译本机械工业出版社 1999 发行

38
----------------------- Page 52-----------------------
2.2 DSL(
特定领域语言
DSL
优势
那么 DSL 哪些优势为什么 DSL 近年来如此备受关注
因为 DSL 几个方面可以掌握提高生产效率关键。DSL 拥有特定领域
词汇可以高级层面编写程序由于需要编写多余部分因此节约程序开发
时间
此外使用 DSL 可以程序整体简洁形式进行表达意味着无论程序还是
程序成本降低同时意味着对于编程专家一般人编程大门正向他们敞开
觉得编程如果自己专业领域适用 DSL 的话情况不同如果
可以计算机完成任务直接描述出来也许可以减少程序员交流成本从而实现
生产效率提高 DSL 备受关注理由
仔细发现涉及对象领域内部细节而是高级层面进行描述就是
半个世纪以来编程语言进化方向——抽象也就是说,DSL ,尤其是内部 DSL ,也许就是
抽象不断推进引领编程语言未来发展方向之一
DSL
定义
谈到 DSL ,大家总是热衷讨论到底怎样算是 DSL 。关于什么 DSL ,什么不是 DSL ,
没有明确定义标准
一种观点认为是否具备用于特定用途功能”、“(设计者自己是否名为
一种 DSL”可以作为判断标准实际上这些标准非常模棱两可
尽管如此考虑 DSL 实际上编程语言抽象延伸那么问题应该什么
DSL 、
什么不是 DSL ,DSL 应该面向特定领域 API 设计优秀 DSL 这样设计
过程

据说诞生 UNIX AT&T 贝尔实验室 一句名言设计就是语言设计(Library
design is language design )。
我们思考编程语言时候大多强调语法如果脱离相当
词汇方法语言无从思考
贝尔实验室(Bell Laboratories )电信相关研究起家研究开发机构AT&T Western Electric 共同
出资创立 1925 1996 脱离 AT&T ,目前属于阿尔卡特(Alcatel-Lucent )公司旗下贝尔实验室
计算机领域重大发明包括 UNIX 操作系统 C 语言其他领域重大发明包括晶体管激光、CCD
许多成果获得诺贝尔奖
39
----------------------- Page 53-----------------------
2 编程语言过去现在未来
也就是说,API (Application Programming Interface ,应用程序编程接口构成编程
重要要素一种语言添加相当于设计一种新增一些词汇规模

一点儿语言”。我们可以通过编程达人大卫 ·托马斯 理解过程
Programming is a process of designing DSL for your own application.
编程就是自己应用程序设计DSL 过程
应用程序开发可以理解组件设计针对该应程序对象领域 DSL ,最后
进行整合过程这样编写出来应用程序代码抽象应对未来修改能力
不错应用程序
因此,DSL 并不仅仅一种技术而是应用程序开发重要设计原理原则之一可以
适用任何软件开发设计 API 如果设计一种 DSL”一样进行设计的话
感触应该变得不同
适合内部 DSL 语言
正如刚才 UNIX 文化若干单一目的工具组成工具箱系统主流
Linux
继承一点。UNIX 中的各种迷你语言作为组成工具箱零部件同时作为
目的专用外部 DSL ,不断发展壮大起来
不过现代 UNIX 文化到了来自外部影响例如现在典型 UNIX 文本
Emacs ,起源与其来自 UNIX ,不如来自美国麻省理工学院(MIT )Lisp

开发者理查德 · 本来就是 MIT 出身 Lisp 黑客因此理所当然
此外 Perl Ruby 这些脚本语言不是采用工具组合起来方式而是提供
多功能可编程工具一点到了 Lisp 文化影响近年来内部 DSL 备受
这个倾向不能没有关联
那么什么样语言适合用作内部 DSL 宿主语言虽然任何语言可以成为宿主语言
Lisp、Smalltalk、Ruby 这样认为适合 DSL 语言拥有一些共同特征
首先简洁由于 DSL 本来就是为了针对特定目的处理高级简洁方式进行描述
大卫托马斯(Dave Thomas )程序员作家出版Programming Ruby
理查德(Richard Stallman ,1953— )著名黑客自由软件运动领导者,GNU 计划自由
基金会创始人编写 GNU 通用公共许可证(GNU GPL )世界广为采用自由软件许可证
40
----------------------- Page 54-----------------------
2.2 DSL(
特定领域语言
因此简洁描述方式本质这个意义上来语言简洁作为 DSL 宿主语言不可
要素。Lisp Ruby 语言无需程序声明数据类型编译器规矩比较
因此能够程序变得简洁
作为宿主语言另一重要特征就是灵活性 DSL 开发者通过高度抽象代码
集中描述 What ,不是 How ,因此作为抽象支持编程功能最好比较充实
此外,Lisp 具备(Macro )功能只要遵循括号进行表达S 表达式语法
实现相当自由表达因此可以 Lisp 语言本身就是一种编程语言
另一方面,Ruby 中的代码(Block )功能可以实现控制结构代码虽然Lisp
那样万能用来实现内部 DSL 还是足够
我们回头看看 1 Rakefile 示例 Rakefile 定义名为 task 方法
名称作为参数通过符号(Symbol )指定这里定义test 任务执行代码
求值这样 Ruby 通过使用代码可以表达一种控制结构
Rakefile
代码之所以看上去简洁 因为 Ruby 语法就是这样宗旨进行设计
相比语法单纯,Ruby 更加重视程序可读性语法是以先决条件确定
例如调用方法参数周围括号可以省略可以通过代码整个一块代码作为
参数传递方法如果说 Ruby 语法
task(:default => [:test])
没有任何冗余简单的话那么 task(:test, &lambda(){
ruby("test/unittest.rb")
1 Rakefile 代码 变成 2 这个样子。 })
这样语法虽说非常简单绝对
2 Rakefile 示例

那么如果一种具备 Lisp Ruby 这样简洁灵活性语言例如 Java ,是不是
不可能用作内部 DSL 的确 Java 这样语言由于必须指定数据类型代码容易变得
非常繁杂而且语法自由度实现 Lisp Ruby 一样内部 DSL 非常困难

然而代码重构闻名马丁 · 提出通过流畅接口”(Fluent interface )方式
Java
这样语言是不是能够实现内部 DSL 一样功能 3 就是展示流畅接口示例
① S
表达式(S-expression )符号表达式(Symbolic Expression )简称Lisp 用于表达嵌套层次
数据方式
马丁(Martin Fowler ,1963— )软件开发方面著名作家这里代码重构
1999 肯特贝克合作编写重构改善既有代码设计》(Refactoring: Improving the Design of
Existing Code )

41
----------------------- Page 55-----------------------
2 编程语言过去现在未来
3 中的代码定义顾客(Customer )
private void makeOrder(Customer
订单订单包含商品及其数量 customer) {
customer.newOrder()
明细某些情况为了避免订单延迟 .with(6, "TAL")
发货可以订单去掉某些货品剩下 .with(5, "HPK").skippable()
.with(3, "LGV")
货品出来因此这里明细 .priorityRush();
}
货品定义赶不上的话可以跳过”。
订单状态设定加急”。 3 整个 3  Java 编写流畅接口(fluentinterface)
代码含义定义如下这样订单
‰ TAL ,6

‰ HPK ,5
跳过
‰ LGV ,3

‰

private void makeOrder(Customer customer) {
Order o1 = new Order();
(Method chain ),
Java
customer.addOrder(o1);
这种表达方式前所未有 OrderLine line1 = new OrderLine(6, Product.
find("TAL"));
如果以前 Java o1.addLine(line1);
表达的话变成 4 OrderLine line2 = new OrderLine(5, Product.
find("HPK"));
这样。 o1.addLine(line2);
OrderLine line3 = new OrderLine(3, Product.
简洁易读区别 find("LGV"));
o1.addLine(line3);
了然虽然流畅接口 Java line2.setSkippable(true);
o1.setRush(true);
没有普及不过这种设计思路 }
今后非常值得期待
4 Java 标准风格接口
外部 DSL 实例
内部 DSL 代表编程语言进化一种形态作为编程爱好者自然兴趣
这里谈谈外部 DSL 。
刚才已经外部 DSL 就是拥有专用引擎一种独立特定领域语言不过外部 DSL
各自不同实现水平
简单一种莫过于配置文件数据文件例如 YAML 、JSON 语言就是为了
对象人类易读形式描述出来特定目的设计外部 DSL (5 )。
42
----------------------- Page 56-----------------------
2.2 DSL(
特定领域语言
复杂一些 DSL ,虽然特定目的
[
设计可以编写描述任意算法程序例如 {
"name": "
本行 ",
用于文本处理 awk 属于这种水平。awk "company": "NaCl",
基本结构文件行为单位读取字符串, "zipcode": "690-0826",
"address": "
松江 2-12-5",
同时具备读取一行之后字符串分割字段 "tel": "0852-28-XXXX"
}
这样文本处理专用功能这些功能明显属于
]
DSL ,
另一方面不是不能编写本处 ---
范围以外程序例子用于打印机 5 外部 DSLJSON 数据表达方式
描述页面 DSL PostScript ,一种基于波兰
图灵完全拥有完整计算能力语言
另外还有一种比较特殊虽然它本身一种通用语言叫做 DSL 并不十分合适
语言特定计算模型之间拥有关联具备类似 DSL 性质例如
Prolog ,
一种谓词逻辑基础语言。Prolog 可以认为面向谓词逻辑特定
DSL ,谓词逻辑这个适用范围称作特定领域似乎未免宽泛因此一般情况
人们不会 Prolog 称作 DSL 。同样用于并行计算Actor 模型密切相关 Erlang 语言
虽然一种通用语言同时具备面向并发编程领域 DSL”性质
觉得比较有意思 Java 使用 XML 。由于 Java 默认内置用于解析 XML
因此如果 XML 编写 DSL 的话可以容易程序读取这样一来基本上
可以省却 DSL 开发语言引擎步骤
通过这样方式我们可以 XML 通用特定目的限制语法容易创造
出新外部DSL ,认为一种非常高效方式只不过,XML 文件内容通过标签描述
看起来十分冗长无论阅读还是编写用户不是友好一点算是遗憾
DSL
设计构成要素
曾经诸多 Ruby 相关活动发表演讲著名 Rubyist——Glenn Vanderburg 认为构成
优秀内部)DSL 要素包括下列 5
波兰记法(Reverse Polish notation )操作符置于操作数后面后缀记法”,“3 + 4”这种
“3 4 +”。这种记法好处不必使用括号表示运算顺序并且对于计算机容易实现
② Actor
模型(Actor model )一种用于并行计算模型其中Actor 并行计算基本单元可以其他
Actor
进行消息通信可以创建 Actor 。
43
----------------------- Page 57-----------------------
2 编程语言过去现在未来
‰
上下文(Context )
‰
语句(Sentence )
‰
单位(Unit )
‰
词汇(Vocabulary )
‰
层次结构(Hierarchy )
其中上下文用来针对 DSL 单独语句规定其所拥有含义也许有人认为:“
参数方式进行指定好了?”不过大家,DSL 宗旨进行简洁描述
每次通过参数反复指定上下文的话程序必定变得冗长
请看 6 中的程序描述测试
class UserTest < Test::Unit::TestCase
shoulda 编写测试程序 context "a User instance" do
setup do
一种 Ruby 基础内部 DSL 。 @user = User.find(:first)
end
6 ,context
should "return its full name" do
should
方法定义上下文顾名思义, assert_equal 'John Doe', @user.full_
context
方法作用就是定义上下文 name
end
这个测试项目框架上下 end
end
范围通过指派 context 方法代码
定义因此 6 shoulda 编写测试程序
context "a User instance" do
表示 a User instance 这个上下文测试进行定义意思
should
方法定义需要满足条件这样测试指派 should 方法代码
描述测试成功视为满足测试条件。should 方法定义测试外部上下文
结合起来定义“a User instance should return its full name”(用户实例应当返回
软件行为
这样方式与其测试不如定义软件应该具有行为(Behavior ),因此
情况人们不会称为测试而是称为规格(Spec )。此外软件开发之前设计
规格开发手法通常不是叫做测试驱动开发而是叫做行为驱动开发(Behavior Driven
Development ,BDD )。
完了上下文下一个 DSL 构成要素语句”。语句也就是上下文独立代码
内部 DSL 实现函数方法调用
貌似英语总是语句尽可能看起来接近英语一点得很重要。DSL 优点之一
44
----------------------- Page 58-----------------------
2.2 DSL(
特定领域语言
就是并非专家普通人能够使用因此编程降低门槛这样带来
可以理解不过作为因为英语不是缘故),觉得看上去
英语不是 DSL 本质不过还是执着一点
几年一次 Ruby 大会
recipe "Spicy Bread" do
DSL 主题段落内容基本 add 200.grams.of Flour 加入200小麦#
add 1.lsp.of Nutmeg
加入肉豆蔻#
上都围绕 Ruby 范围 继续……#
到底设计接近英语 DSL”这个 end
话题各种角度觉得 7 描述面包做法 DSL
意思例如如果用来描述面包做法 DSL 成像 7 这样好不好说实话
没有什么自信
话说如果忽略符号的话作为英语理解确实没有问题接近自然语言
作为行人容易纠结一些微妙差异例如到底哪里加上符号顺序不能调换
等等觉得正是 COBOL 曾经经历坎坷英语人们似乎有着不同感触理解
接下来要素单位也就是 7 例子出现(grams )、(lsp )等等
由于 Ruby 可以有的自由添加方法利用一点上面例子实际上
整数定义 grams 方法 lsp 方法
第一次见到这样扩展功能 Ruby on Rails ①包含 ActiveSupport 当时
看到现在时间 20 小时之前居然能够“20.hours.ago”时候感到非常震惊实际上
整数中的 hours 方法 20.hours 调用返回 72000 (3600 ×20 )这个ago
返回表示之前时刻 Time 对象而已
这样看来,Rails 不仅一种 Web 应用程序框架同时可以是以 Web 应用程序开发
对象领域 Ruby 基础内部 DSL 。
Glenn Vanderburg
另外要素就是词汇层次结构前者意思目的领域
多少适用方法必要方法自动生成功能包含在内
例如 Rails 如果数据库 users 包含 name 属性的话那么可以进行
调用
① Ruby on Rails
简称 Rails ,Ruby 语言编写非常有名开源 Web 开发框架遵循 MVC 模式提出
重复”、“惯例优于配置理念这些理念后来其他语言编写一些 Web 开发框架 CakePHP 、
Zend
借鉴
45
----------------------- Page 59-----------------------
2 编程语言过去现在未来
User.find_by_name("
松本 ")
其中 find_by_name 方法就是自动生成
层次结构可以理解嵌套上下文
Vanderburg
8 这样例子使用 XmlMarkup 例子 Ruby
一种 DSL 来生 XML ,看起来可能 XML 代码易读 8 代码
XmlMarkup
生成 XML 文件内容 9
xml = Builder::XMLMarkup.new
xml.html { <html>
xml.head{ <head>
xml.title("History") <title>History</title>
} </head>
xml.body { <body>
xml.h1("Header") <h1>Header</h1>
xml.p("paragraph") <p>paragraph</p>
} </body>
} </html>
8  XmlMarkup DSL 生成 XML 9 XmlMarkup 生成 XML
Sinatra
觉得 Rails 具备 DSL 性质不是有意而是提高生产效率追求抽象
结果”, Rails 更新 Web 应用程序框架出现一些明显DSL 有所意识项目
其中代表就是 Sinatra。
Sinatra
位于规模 Web 应用程序框架 Sinatra 编写用来显示“Hello, world!”
字符串应用程序 10 对于 Web 浏览器传送过来“get /”请求应当如何响应正是
DSL 形式表达这种十分简洁表达方式以及应用上下文语句代码风格
Rakefile、shoulda
这些 Ruby 基础内部 DSL 可以异曲同工
# hello_world.rb
require 'sinatra'
get '/' do
"Hello world, it's {Time.now} at the server!"#
end
10  Sinatra 编写 Web HelloWorld
46
----------------------- Page 60-----------------------
2.2 DSL(
特定领域语言
小结
DSL
Ruby 备受关注一种方式或者可能有点。DSL 这个称呼虽然只是
最近出现实际上已经年前开始已经使用技术
不过正如通过一种设计模式名字能够提高认知从而使用一样
DSL
可能因为赋予这个名字获得广泛认知从而编程生产效率提高做出贡献
今后,DSL 可能作为判断设计优秀与否重要指标软件开发产生巨大影响
47
----------------------- Page 61-----------------------
2 编程语言过去现在未来
2.3
编程
Ruby 编程再度成为热点话题。Ruby on Rails 框架生产就是
编程实现一点其实已经老生常谈不过,《Ruby 编程》① 2010
ASCII Media Works
出版日文因此貌似通过重新认识编程
2010 RubyKaigi ②有幸到了《Ruby 编程作者 Paolo Perrotta 日本
演讲简单好像没有 Lisp 经验感到非常意外那么我们
参考作为原点 Lisp ,一边重新审视一下编程
Meta, Reflection
这个来自希腊语表示……之间……之后超过……”前缀
meta ,
具有超越高阶意思这个意思引申出来单词前面加上meta ,表示自身描述
例如描述数据具有结构数据也就是关于数据本身数据称为元数据(Metadata )。
比较特别例子小说中的角色如果知道自己身处故事虚构这样小说

称为小说(Metafiction ) 。
综上所述我们可以推论所谓编程就是程序编写程序意思那么
编写程序什么意义
C 这样 编程语言语言本身提供数据基本上通过指针地址数值
表现语言层面虽然有数结构概念经过编译之后这些信息丢失
不过,“现代派语言运行时候保留这样一些信息例如 C++
知道自己数据类型通过这个信息可以调用虚拟成员函数选择自己类型
匹配函数 Java 一样
① 《Ruby
编程》(Metaprogramming Ruby ),Paolo Perrotta 出版 2010 2 中文 2012 1
华中科技大学出版社出版
② RubyKaigi ,
原名日本 Ruby 会议”,关于 Ruby 编程语言集会活动 2006 开始每年日本举办
常用中文后设小说”,小说”、“自反小说”。
48
----------------------- Page 62-----------------------
2.3 
编程
这样获取变更程序本身信息功能称为反射(Reflection )。程序获取自身信息
行为镜子射出身影反省自己这样语境表达听起来文艺
Java 、C++ 语言相比 Ruby 大部分信息可以容易进行访问操作作为例子
我们定义创建相应对象开始
class Duck
def quack
"quack"
end
end
duck = Duck.new
我们 class 语句定义名为 Duck Ruby 普通对象也就是说
duck = Duck.new
表示调用 Duck 这个对象中的 new 方法 new 方法调用结果就是生成返回
Duch 实例
生成出来实例包含作为信息这些信息可以通过调用 class
查询
duck.class => Duck#
调用实例对象方法实例寻找自己
duck.quack => "quack"#
换句话说调用 duck quack 方法首先找到 duck (Duck ),然后找到这个
定义 quack 方法于是由于 Duck 定义 quack 方法因此我们便能够调用
Duck
定义方法只有 quack 我们确认一下调用 instance_methods 方法
可以得到定义方法一览
Duck.instance_methods(false)
# => [:quack]
参数 false 表示显示定义方法如果指定这个参数,Duck
继承过来方法一起显示出来
调用方法如果这个方法没有定义寻找相应方法例子
请看下面代码
49
----------------------- Page 63-----------------------
2 编程语言过去现在未来
duck.to_s => " <Duck:0xb756ed2c>"# #
由于 Duck 没有定义 to_s 方法因此 Duck 寻找可是,Duck
什么定义 Duck 时候我们没有指定
这样信息 Ruby 获得准确答案 ancestors 方法可以得到相当于
一览
Duck.ancestors
# => [Duck,Object,Kernel,BasicObject]
这里我们可以看出,Duck Object class 语句如果指定的话
表示 Object 作为
那么,to_s 是否 Object 进行定义
Object.instance_methods(false)
# => []
,Object 方法没有定义其实 to_s 这样所有对象共享方法
上层 Kernel 模块进行定义
到此为止对象结构 1 因此 Ruby 便是这样对象可以
一般对象一样进行操作不对,“……一样这个说法不准确 Ruby 其他
对象完全相同没有任何区别
BasicObject
Kernel
to_s(), class()
调用方法 Object
方法
向上
搜索
实例 Duck
duck quack
1 对象结构(Ver.1)
50
----------------------- Page 64-----------------------
2.3 
编程
对象
可是如果说其他对象完全没有任何区别的话那么什么我们还是
Ruby
Duck.class => Class#
也就是说 Ruby 名叫 Class 所有可以看做这个 Class 实例
有点绕口令
刨根问底一下如果所有 Class 实例那么 Class 什么
Class.class => Class#
Class
居然 Class ,也就是自己本身真是出乎意料我们看看 Class
什么查看直接一级可以使用 superclass 方法
Class.superclass => Module#
原来 Class Module Module Object。
Module.superclass => Object #
上面所有信息综合起来更新之后对象结构 2 这样可以直观
看到 Ruby 对象具有层次结构
BasicObject
Kernel
Object Module
duck Duck Class
2 对象结构(Ver.2)
51
----------------------- Page 65-----------------------
2 编程语言过去现在未来
操作
不过即便一种对象定义方法定义专用语法”,消除
Ruby 有的特殊印象我们看看上面 Duck 定义
class Duck
def quack
"quack"
end
end
其实 Ruby 其他部分一样通过方法调用可以实现同样操作
Duck = Class.new do
define_method :quack do
"quack"
end
end
怎么样?“创建 Class 实例然后赋值 Duck 常量”,“ quack 方法进行
”,通过这样步骤是不是直接感触不过这种写法实在易读
不会推荐大家这样这种写法只是能够帮助直观地理“Ruby Class 完全
Ruby 对象概念而已我们仔细看看 define_method 部分上面代码中的
2
4 内容下面 def 语句等同
def quack
"quack"
end
不愧专用语法 def ,代码就是简洁这里define_method 方法执行具体操作如下
‰
参数方式接收表示方法符号(:quack )
‰
代码形式接收方法定义部分
运行这个方法实体什么透露一下答案运行这个方法主体就是 Duck
方法定义部分(class 代码、Class.new 代码,self 表示现在正在
这样定义可以通过调用 define_method 方法自身添加方法
不过肯定有人这种 def 更加冗长方法到底什么意义因为通过调用
定义方法可以我们打开可能性
例如假设某种情况需要定义 foo0 ~foo99 这样 100 方法程序 100 def
语句实在辛苦如果 Java 的话通过代码生成也许可以用不着真的看到 100
52
----------------------- Page 66-----------------------
2.3 
编程
方法定义拥有 define_method Ruby 我们下面这样简单程序可以定义 100
方法
100.times do|i|
define_method("foo {i}") do#
...
end
end
循环代替 100 定义 define_method 真正用武之地
这个方法 self 表示现在正在定义利用性质 Ruby 可以实现各种各样
例如
‰
指定定义方法怎样范围可见(public, protected, private )
‰
定义实例变量访问(attr_accessor, attr_reader, attr_writer )
这样本来属于声明内容通过调用方法可以实现一点 Ruby 长处
Lisp
拥有方面长处语言并不只有 Ruby ,Lisp 可以这种语言祖宗。Lisp 历史相当
悠久诞生可以追溯 1958说起 1958那个时候其他编程语言几乎没有出现
那个时代已经存在并且现在依然编程语言只有 FORTRAN (1954
COBOL (1959
而已。Lisp 作为编程语言特殊在于原本不是作为一种编程
语言而是作为一种数学计算模型设计出来。Lisp 设计者约翰 ·当时没有设想
用作一种计算机语言实验室研究生—— ·罗素 IBM 704
机器语言实现原本只是作为计算模型编写万能函数 eval ,这里,Lisp 真正为了
编程语言
Lisp
在编语言可以类似 OOPArts ①一样东西编程语言历史机器语言
语言开始逐步发展 FORTRAN 、COBOL 这样高级语言这样历史
古老语言之一 Lisp ,居然一下子具备超越当时时代多功能
1995
Java 诞生 时候虚拟机异常处理垃圾回收这些概念感到耳目一新
这些技术普及一般人这个角度,Java 功绩相当伟大实际上所有
① Out Of Place Artifacts
缩写意思时代不符使用先进技术遗物”。(
53
----------------------- Page 67-----------------------
2 编程语言过去现在未来
技术早在 Java 诞生年前真的年前),就是已经 Lisp 到了实现
通过 Java 知道垃圾回收 Lisp 早期解释器已经具备垃圾回收机制由于
Lisp
数据作为对象处理内存分配不是指定因此垃圾回收机制不可或缺
于是 40 多年技术
(Virtual machine )、字节解释器(Bytecode interpreter )这些词汇通过
Java
普及它们其实 Smalltalk 使用技术。Smalltalk 实现可以追溯 20
70 年代 80 年代初因此技术到了 Lisp 影响只要看看发现,Smalltalk
解释器 Lisp 解释器简直模子出来
数据程序
凡是 Lisp 程序恐怕都会感慨这个语言里面怎么这么括号”。 3 显示
就是用于阶乘计算 Lisp 程序 4 Ruby 功能相同程序大家可以比较一下
括号的确尤其是表达式结束部分括号相当醒目这种 Lisp 表达式写法
称为 S 表达式不过除此之外部分基本上可以一一对应值得注意下面几点
‰ Lisp
通过括号体现语句表达式
‰ Lisp
没有通常运算而是全部采用括号起来函数调用形式
‰ “1-”
用来参数 1 函数
#
这里体现 Lisp Ruby相似性
def fact(n)
if n == 1
;;;
通过归纳法定义阶乘计算 1
(defun fact(n) else
(if (= 1 n) n fact(n - 1)*
1 end
( n (fact (1- n)))))* end
(fact 6) ;; =>
结果720 fact(6) => 结果720#
3 Lisp 编写阶乘程序 4 Ruby 编写阶乘程序
Lisp 重要数据类型(List ),甚至Lisp 这个名字本身 List Processor
称为单元(Cell )数据连接起来构成5 )。单元包含
叫做 car ,另一叫做 cdr。它们可以其他单元引用或者称为原子(Atom )
单元例如数值字符串符号属于原子
54
----------------------- Page 68-----------------------
2.3 
编程
:(1 2 3)这样实际结构
car cdr
单元
1
2
3 nil
5 Lisp
S
表达式用来描述这种记法遵循规则语法)。首先单元(Dotted
pair )描述例如,car cdr 数值 1 单元下面这样
(1 . 1)
其次,cdr 部分如果省略括号也就是说
(1 . (2 . 3))
应该
(1 2 . 3)
然后如果 cdr 部分 nil ,省略 cdr 部分于是
(1 2 3 . nil)
应该
(1 2 3)
S
表达式基本规则只有上面这些只要理解上述规则可以通过括号罗列
想象实际结构掌握规则之后 5 ,应该能够理解更加清楚
那么这里重要一点,Lisp 程序通过 S 表达式进行表达换句话说,Lisp 程序
通过 Lisp 本身频繁操作方式表达意味着程序数据完全等同
非常符合编程概念实际上编程已经深深融入 Lisp 之中成为本质一部分
55
----------------------- Page 69-----------------------
2 编程语言过去现在未来
Lisp
程序
Lisp
程序形式(Form )排列起来构成形式就是S 表达式通过下面规则
求值
‰
符号(Symbol )解释变量变量绑定
‰
符号以外原子其自身整数的话就是整数本身字符串的话
字符串本身
‰
如果形式头一符号函数”,剩余元素参数① 。
形式表示函数部分实际上分为函数特殊形式种类它们各自
行为有所区别函数相当于 C 语言中的函数或者 Ruby 中的方法参数求值函数
调用特殊形式相当于其他语言中的控制结构这些结构无法通过函数表达
例如,Lisp 用于赋值 setq 特殊形式写法如下
(setq a 128)
假设 setq 函数那么 a 作为参数求值不会变量 a 进行赋值。setq 并不
a 进行求值而是作为变量对待 Lisp 语言直接设定规则这样
特殊待遇形式称为特殊形式 setq 以外特殊形式还有用于条件分支 if 用于
定义局部变量 let 。

Lisp 介绍篇幅预想其实真正想要介绍就是这个”(Macro )。Lisp
中的可以对表求值通过构成程序进行操作从而改写程序本身首先
我们函数比较
首先我们看看参数进行平方计算函数 square (6
(defun square (x)
),以及参数进行平方计算 square2 (6 定义。 ( x x))*
看出区别? (defmacro square2 (x)
(list ' x x))*
函数定义使用 defun (def function 缩写),
6 函数定义定义
defmacro ,一点区别另外返回不是
CommonLisp 称为 Lisp-2 系列 Lisp 中的行为,Scheme 属于 Lisp-1 系列语言行为别的

56
----------------------- Page 70-----------------------
2.3 
编程
求值结果而是形式返回调用地方嵌入表达式例如如果
(square2 2)
进行求值的话,Lisp 找到 square2 ,发现首先 2 作为参数
square2
本身进行求值。list 作为参数传递形式返回函数
(list ' x x) ;; => ( 2 2)* *
然后这个结果嵌入调用 square2 地方进行实际求值
虽说 square square2 如果参数没有副作用的话),方法基本上没什么区别
通过使用获取参数加工然后嵌入技术只要遵循 S 表达式语法可能性
无限无论创建控制结构还是 Lisp 创建其他语言内部 DSL )十分
应手
实现这种只要 S 表达式范围便无所不能性质 Lisp 重要特性之一
实际上包括 CommonLisp 用于函数定义 defun 在内语言设计规格相当一部分
通过实现
那么我们想想看有没有只有通过 (defmacro inc (var)
实现例子 7 程序指定变量 (list setq var (list 1+ var)))' '
1 inc 。 7 inc
变量内容1”这样操作由于
赋值操作一般函数无法实现 ;;; (a) inc调用
(setq a 41) ;;
变量a初始化
使用可以容易实现这样扩展 (inc a) ;; a变为42
。inc 实际使用例子 8 (a) 部分。 ;;; (b) 查看inc实体
;;;
macroexpand函数可以查看展开结果
展开结果可以 macroexpand 函数 (macroexpand '(inc a))
;;; => (setq a (1+ a))
8 (b) 部分我们 macroexpand
函数查看展开结果展开结果 8 inc 使用
setq 赋值语句我们这个非常简单如果复杂便想象展开结果
什么样子因此 macroexpand 函数对于调试非常有效

正如刚才,Lisp 非常强大展现强大还有例子就是 CLOS
(Common Lisp Object System )。
57
----------------------- Page 71-----------------------
2 编程语言过去现在未来
CommonLisp 第一没有提供面向对象功能第二规格默认
这个名为 CLOS 面向对象功能这个功能实现通过手段 CommonLisp
自身完成。CommonLisp (及其实在强大语言本身没有进行增强情况
可以定义出面对象功能之所以默认包含语言规格只是为了消除因为实现方法不同
产生安定因素实现方法严密格式成了文档而已① 。
而且,CLOS 不是只是出来玩具而是真正意义拥有大量丰富
功能复杂面向对象系统实现同时代其他语言到现在为止未能实现功能② 。
例如,Ruby 虽然一种非常灵活动态语言面向对象功能方式
语言本身能力实现面向对象功能做不到这样功能,Lisp 仅仅通过
语言本身能力定义出来不得不 Lisp 简直强大令人发指
既然如此强大为什么 Ruby 其他语言没有采用 Lisp 风格
原因语法问题。Lisp 强大力量源于程序数据采用相同结构一点
然而与此同时,Lisp 程序充满括号不是一般程序员熟悉习惯语法
作为语言设计者自己语言是否采用 S 表达式重大决策为了强大
牺牲语法易读易懂做出这样判断十分困难
没有采用 S 表达式语言有一些提供功能例如 C (C++ )中的
处理器(Preprocessor )实现不过这种方式只能简单字符串替换无法编写复杂
以前美国苹果公司开发一种叫做 Dylan ③ 语言采用 Algol 类似比较一般语法
实现做出尝试不过由于诸多原因没有普及夭折
另一难点在于如果采用程序解读变得困难
优点在于包括控制结构定义在内只要 S 表达式语法范围可以实现任何
功能这些功能限于增强语言描述能力提供内部 DSL (特定领域语言而已此外
作者译者电子邮件内容做出如下补充由于 CLOS CommonLisp 内置功能
实现因此实际上任何可以不同方法实现类似功能为了 CLOS 能够解释器通用因此
文档其实方式进行标准化当然并不所有 CommonLisp 解释器中的 CLOS 通过实现
出于速度方面优化考虑 CLOS 直接嵌入解释器中的做法常见
② CLOS
定义 1988 。(
③ Dylan (
名称来自DYnamic LANguage 缩写一种范式跨平台编程语言苹果公司 20 世纪 90 年代初
开始开发后来项目终止发布技术版本后来 Harlequin 公司卡内基梅隆大学团队分别发布
Dylan
Windows Unix 版本目前开源社区 Open Dylan 运营维护
58
----------------------- Page 72-----------------------
2.3 
编程
没有什么高级用法不过反过来说意味着如果具备提供语法
知识把握程序含义如果 Lisp 高手谈谈关于的话他们大概异口同声
:“千万不能多用只能关键时刻一下。”起来编程本身差不多这样
趋势
不过作为 Ruby 语言 设计者依我看使用目的一部分主观判断大约
情况其实可以通过 Ruby 代码实现看法这个角度
Ruby
提供功能实际上大于然而追求强大功能程序员天性
听到希望 Ruby 增加功能意见据说甚至有人通过修改 Ruby 解释器已经功能
出来……
编程可能性危险
Ruby Lisp 这样语言由于程序本身信息可以访问因此程序运行
可以程序本身进行操作就是编程使用编程技术可以实现通常情况无法
实现操作例如,Ruby on Rails 数据库适配器 ActiveRecord 可以读取数据库结构通过
编程技术运行时添加用于访问数据库记录方法这样一来即便数据库结构发生变化
软件没有必要做出任何修改
Builder 。Builder (Mark-up
language )
代码应用例如9
require 'builder'
builder = Builder::XmlMarkup.new
xml = builder.person {|b|
b.name("Jim")
b.phone("555-1234")
}
#=> <person><name>Jim</name><phone>555-1234</phone></person>
9 Builder 应用
9 示例,person name 、phone 标签作为方法调用这些方法不是
Builder 定义由于 XML (Extensible Markup Language ,可扩展标记语言没有
事先规定使用哪些标签因此标签进行预先定义不可能于是 Builder
通过编程技术钩子(Hook )截获调用方法生成所需标签
无论 ActiveRecord 示例还是 Builder 示例通过编程技术无法预先确定
59
----------------------- Page 73-----------------------
2 编程语言过去现在未来
进行应对这样一来未来可能性不会禁锢体现语言灵活性认为这种
灵活性正是编程力量
另一方面编程技术如果编写出来程序一下子明白例如
Builder
源代码怎么找不到定义 person 方法部分如果没有编程知识的话
理解源代码困难一样编程使用需要掌握充分知识遵守用量用法
小结
《Ruby 编程之后印象下面
根本没有什么编程只有编程而已。(弟子茅塞顿开
的确如此程序数据结构算法构成然而如果环境允许程序本身作为数据结构
操作的话那么编程和面一般数据结构一般操作没什么两样作为 Lisp
Ruby
这样允许程序结构进行访问语言所谓编程实际上不是什么特殊东西
只不过日常编程一部分罢了
60
----------------------- Page 74-----------------------
2.4 
内存管理
2.4
内存管理
现实世界总有这样那样局限制约计算机人类那些局限解放出来——
试图努力实现这个目标然而计算机现实世界存在一部分当然本身
制约因此计算机只是提供一种幻觉我们人类以为自己已经这些制约解放出来
看似无限内存
我们具体比如说内存大家电脑上面多大内存最近电脑内存
大多几个 GB 笔记本电脑存有 8GB 。上高中的时候电脑只有 32KB
RAM ,8GB
容量相当于 25 也就是说 30 年中一般人可以获得内存容量
30 年前 25 。25 ……
不过无论内存容量多大总归不是无限实际上随着内存容量增加软件
内存开销同样速率增加因此最近计算机系统通过双重幻觉我们
内存容量无限
第一幻觉垃圾回收(GC )机制关于一点我们稍后详细讲解
第二幻觉操作系统提供虚拟内存由于硬盘容量远远大于内存(RAM ),虚拟
内存正是利用一点在内容量不足经常访问内存空间中的数据硬盘
账面内存容量手段现在虽说内存容量已经增加不过区区
GB 而已相对便是笔记本电脑硬盘已经 GB 容量超过 1TB (1000GB )
开始出现虚拟内存也就是利用这样容量差异
书桌文件地方文件所谓虚拟内存好比书桌比较
文件暂时收到抽屉出来地方摊开文件
不过如果书桌抽屉之间频繁进行文件交换工作效率肯定下降如果每次要看
文件收拾书桌抽屉里面的话工作根本无法进行虚拟内存同样
缺点硬盘容量内存相对速度非常缓慢如果硬盘之间数据交换过于
61
----------------------- Page 75-----------------------
2 编程语言过去现在未来
频繁处理速度下降表面上看起来卡住一样这种现象称为抖动(Thrushing )。
应该计算机停止响应经历造成死机主要原因之一就是抖动
GC
基本方式
好了下面我们来讲 GC (Garbage Collection )。Java Ruby 这样语言程序
运行时创建对象编程语言角度它们对象计算机角度它们
也就是一些装有数据内存空间而已
C C++ 这样 语言这些内存空间手动进行管理需要内存空间
请求操作系统进行分配需要时候还给操作系统然而正是不再需要一点
带来各种各样麻烦
因为不再需要还给操作系统内存空间操作系统重新利用如果小心访
这些空间的话里面数据改写造成程序异常行为甚至崩溃反过来说
如果认为某些内存空间可能还给操作系统或者用完了却忘记返还这些
无法访问空间一直保留下来造成内存白白浪费最终引发性能下降产生抖动
结果管理大量分配内存空间非常困难
内存管理尤其是内存空间释放实现自动化就是 GC。GC 其实古老技术
20 世纪 60 年代开始研究发表不少论文技术大学实验室别的地方已经
时间但是可以 20 世纪 90 年代 Java 出现之后一般程序员有缘接触
在此之前技术只是少数专利
术语定义
讲解 GC 技术之前我们定义即将术语
1.
垃圾
所谓垃圾 (Garbage ),就是需要回收对象作为编写程序可以做出这个对象
已经不再需要这样判断计算机做不到因此如果程序通过变量等等
可能直接间接引用对象那么这个对象视为存活”;相反已经引用不到
对象视为死亡”。这些死亡对象出来然后作为垃圾进行回收就是GC 本质
62
----------------------- Page 76-----------------------
2.4 
内存管理
2.

所谓(Root ),就是判断对象是否引用起始至于哪里不同语言
不同规定基本上变量运行空间作为
好了上面术语我们来讲主要 GC 算法
标记清除方式
标记清除(Mark and Sweep )开发GC 算法( 1960 )。原理非常简单
开始可能引用对象递归方式进行标记然后没有标记对象作为垃圾
回收
1 显示标记清除算法大致原理初始状态
(1)
1 中的 (1) 部分显示随着程序运行分配 Ⓐ Ⓑ Ⓓ
对象状态对象可以其他对象进行引用
Ⓒ Ⓔ
(2) 部分,GC 开始执行开始可能
标记阶段
引用对象标记”。大多数情况这种标记
(2)
对象内部标志(Flag )实现于是标记 A Ⓑ Ⓓ

我们它们
Ⓒ Ⓔ
(3) 部分标记对象能够引用对象 (3)
标记重复步骤的话可以开始可能 A B Ⓓ
间接引用对象全部标记到此为止操作
C Ⓔ
标记阶段(Mark phase )。标记阶段完成时标记
清除阶段
对象视为存活对象
(4)
Ⓐ Ⓑ
1 中的 (4) 部分 全部对象顺序扫描一遍
没有标记对象进行回收操作称为清除阶段
(Sweep phase )。
扫描同时需要存活对象 ●:标记对象
清除以便下一次 GC 操作准备 1 标记清除算法
标记清除算法处理时间存活对象对象总数总和相关
作为标记清除变形还有一种叫做标记压缩(Mark and Compact )算法不是
标记对象清除而是它们不断压缩
63
----------------------- Page 77-----------------------
2 编程语言过去现在未来
复制收集方式
标记清除算法缺点就是分配大量对象并且其中只有一小部分存活情况
消耗时间大大超过必要因为清除阶段需要大量死亡对象进行扫描
复制收集(Copy and Collection )试图克服缺点这种算法开始
对象复制另外空间然后复制对象能够引用对象递归方式不断
下去
2 (1) 部分 GC 开始内存状态 (1)
Ⓐ Ⓑ Ⓓ
1 (1) 部分一样 2 (2) 部分
对象所在空间以外准备一块空间”,
Ⓒ Ⓔ
可能引用对象复制空间
(2)
(3)
部分已经复制对象开始可以 Ⓐ Ⓑ Ⓓ

对象糖葫芦一样复制空间复制 空间
之后,“死亡对象空间 (4) Ⓒ Ⓔ
部分 空间废弃可以死亡对象占用

空间一口气全部释放出来没有必要再次扫描 空间
对象下次 GC 时候现在空间变成
将来空间。 (3)
Ⓐ Ⓑ Ⓓ

通过 2 我们可以发现复制收集方式 空间
相当于标记清除方式中的标记阶段由于清除阶段 Ⓒ Ⓔ
需要现存所有对象进行扫描存在大量对象, Ⓐ Ⓑ

大部分即将死亡情况全部扫描一遍 空间
开销实在。 Ⓒ
(4)
复制收集方式存在这样开销 Ⓐ Ⓑ

标记相比对象复制需要开销

较大因此存活对象比例情况反而
2 复制收集算法
比较不利
这种算法另一好处具有局部性(Locality )。复制收集过程按照对象
顺序对象复制空间于是关系对象距离内存空间中的可能
提高称为局部性局部性情况内存缓存容易有效运作程序运行
能够得到提高
64
----------------------- Page 78-----------------------
2.4 
内存管理
引用计数方式
引用计数(Reference Count )方式GC 算法简单容易实现一种标记清除
方式差不多同一时间发明出来
基本原理对象保存对象引用计数引用发生增减计数进行更新
引用计数增减一般发生变量赋值对象内容更新函数结束局部变量不再引用
时间点对象引用计数变为 0 说明将来不会引用因此可以释放相应
内存空间
3 (1) 部分 所有对象中都保存自己 (1) I I I
其他对象进行引用数量引用计数), Ⓐ Ⓑ Ⓓ
右上数字就是引用计数。 2 I
Ⓒ Ⓔ
引用
(2) 部分对象引用发生变化引用 (2) I I 失效 O
跟着变化这里对象 B 对象 D 引用失效 Ⓐ Ⓑ Ⓓ
于是对象 D 引用计数变为 0。由于对象 D 引用 I O
Ⓒ Ⓔ
计数 0 ,因此对象 D 对象 C E 引用分别
相应减少结果对象 E 引用计数变为 0 ,于是 (3) I I
Ⓐ Ⓑ
E 释放。 I

3 (3) 部分引用计数变为 0 对象释放,“
■ :
引用计数
对象保留下来大家应该注意整个 GC
3 引用计数算法
处理过程并不需要所有对象进行扫描
实现容易引用计数算法优点标记清除复制收集这些 GC 机制实现上都
难度引用计数方式的话凡是有些年头 C++ 程序员包括在内),应该曾经实现
类似机制可以这种算法相当具有普遍性
除此之外对象不再引用瞬间释放优点其他 GC 机制
预测对象何时释放困难引用计数方式立即释放而且
释放操作针对对象个别执行因此其他算法相比 GC 产生中断时间(Pause
time )
比较优点
引用计数方式缺点
另一方面这种方式缺点引用计数缺点就是无法释放循环引用对象 4
65
----------------------- Page 79-----------------------
2 编程语言过去现在未来
A 、B 、C
对象没有其他对象引用而是互相之间循环引用, I

因此它们引用计数永远不会 0 ,结果这些对象永远不会
释放。 I I
Ⓑ Ⓒ
引用计数第二缺点就是必须引用发生增减 4 无法回收循环引用
计数做出正确增减如果漏掉增减的话
找到原因内存错误引用增加的话恰当对象进行释放引用
减少的话对象一直残留在内从而导致内存泄漏如果语言编译器本身引用计数
进行管理的话还好否则如果手动管理引用计数的话成为孕育 bug 温床
最后缺点就是引用计数管理并不适合并行处理如果多个线程同时引用计数进行
增减的话引用计数可能产生一致问题结果导致内存错误)。为了避免这种
情况 发生引用计数操作必须采用独占方式进行如果引用操作频繁发生每次
使用并发控制机制的话开销不可小觑
综上所述引用计数方式原理实现虽然简单缺点因此最近基本上不再使
现在依然采用引用计数方式语言主要 Perl Python ,它们为了避免循环引用
问题配合使用其他 GC 机制这些语言,GC 基本上通过引用计数方式进行
偶尔其他算法执行 GC ,这样可以引用计数方式无法回收那些对象处理
进一步改良应用方式
GC
基本算法大体上逃不出上述方式以及它们衍生现在通过
进行融合出现一些更加高级方式这里我们介绍一下其中代表性
回收增量回收并行回收有些情况可以这些方法中的进行组合使用
回收
首先我们来讲高级 GC 技术重要一种回收(Generational GC )。
由于 GC 程序处理本质无关因此消耗时间回收目的
正是为了程序运行期间 GC 消耗时间尽量缩短
回收基本思路利用一般性程序具备性质大部分对象都会短时间
成为垃圾经过一定时间依然存活对象往往拥有寿命如果寿命对象容易
66
----------------------- Page 80-----------------------
2.4 
内存管理
下来寿命对象废弃那么到底怎样才能 GC 变得更加高效如果
分配不久诞生时间年轻对象进行重点扫描应该可以有效回收大部分垃圾
回收对象按照生成时间进行刚刚生成不久年轻对象新生代(Young
generation ),
存活时间对象老生(Old generation )。根据具体实现方式不同
可能划分这里为了讲解方便我们限定如果上述关于对象寿命
假说成立的话那么只要仅仅扫描新生代对象可以回收废弃对象中的一部分
这种扫描新生代对象回收操作称为回收(Minor GC )。回收具体回收步骤
如下
首先开始一次常规扫描找到存活对象这个步骤采用标记清除或者复制收集
算法可以不过大多数回收实现采用复制收集算法需要注意扫描
如果遇到属于老生对象不对对象继续进行递归扫描这样一来需要扫描
对象数量大幅减少
然后第一次扫描残留下来对象划分老生具体如果复制收集算法
的话只要复制目标空间设置老生可以标记清除算法的话大多采用
设置某种标志方式
来自老生引用进行记录
这个时候问题出现老生对象 新生代 老生
记录
新生代对象引用怎么办如果扫描 Ⓐ Ⓑ Ⓔ Ⓕ
区域的话那么老生新生代引用 (1)
不会检测这样一来如果年轻 Ⓒ Ⓓ
对象只有来自老生对象引用 世代 老生
记录
已经死亡因此回收 Ⓔ Ⓕ
对象更新进行监视老生新生代 (2) Ⓓ
引用记录叫做记录(remembered Ⓒ Ⓐ Ⓑ
set )
5 )。执行回收过程 5 回收方式中的回收
任何地方没有进行引用老生中的 F 对象通过
这个记录作为对待操作进行回收
回收正确工作必须使记录内容保持更新为此老生新生代引用
产生瞬间必须引用进行记录负责执行这个操作子程序需要嵌入所有
对象更新操作地方
67
----------------------- Page 81-----------------------
2 编程语言过去现在未来
这个负责记录引用子程序这样工作设有对象:A B ,A 内容进行改写
加入 B 引用如果① A 属于老生对象,② B 属于新生代对象引用添加
集中
这种检查程序需要所有涉及修改对象内容地方进行保护因此称为屏障(Write
barrier )。
屏障不仅用于回收同时其他GC 算法
虽说老生区域中的对象一般来说寿命比较不是老不死随着程序
运行老生区域中的死亡对象不断增加为了避免这些死亡老生对象白白
内存空间偶尔需要包括老生区域在内全部区域进行一次扫描回收这样全部
对象 GC 操作称为完全回收(Full GC )或者回收(Major GC )。
回收通过减少 GC 扫描对象数量达到缩短 GC 带来平均中断时间效果
由于还是需要进行回收因此中断时间没有得到什么改善吞吐对象
寿命假说能够成立程序由于扫描对象数量减少可以达到非常不错成绩但是
性能程序行为代数量回收触发条件因素大幅度左右
增量回收
实时要求程序比起缩短 GC 平均中断时间往往重视缩短 GC
中断时间例如机器人姿势控制程序如果因为 GC 控制程序中断 0.1
可能摔倒或者如果车辆制动控制程序因为 GC 延迟响应的话后果不堪

这些实时要求程序必须能够 GC 产生中断时间做出预测例如
可以最多只能中断 10 毫秒作为附加条件
一般 GC 算法 作出这样保证不可能因为 GC 产生中断时间对象
状态有关因此为了维持程序实时等到 GC 全部完成而是 GC 操作分成
多个部分逐一执行这种方式称为增量回收(Incremental GC )。
增量回收由于 GC 过程渐进回收过程程序本身继续运行对象之间
引用关系可能发生变化如果已经完成扫描标记对象修改对象产生引用
这个对象不会标记明明存活对象回收
增量回收为了避免这样问题回收一样采用屏障已经标记
引用关系发生变化通过屏障引用对象作为扫描起始记录下来
68
----------------------- Page 82-----------------------
2.4 
内存管理
由于增量回收过程渐进可以中断时间控制一定长度之内另一方面
由于中断操作需要消耗一定时间,GC 消耗时间相应增加所谓有失
并行回收
最近计算机一块芯片搭载多个 CPU 核心处理器已经逐渐普及不仅服务
个人桌面电脑 CPU 已经成了家常便饭例如美国英特尔公司 Core i7
拥有 6 12 线程
这样环境需要通过利用线程充分发挥 CPU 性能并行回收正是通过
限度利用 CPU 处理能力进行 GC 操作一种方式
并行回收基本原理有的程序运行同时进行 GC 操作一点增量回收
相似不过对于 CPU 进行 GC 任务分割增量回收并行回收可以利用
CPU
性能尽可能这些 GC 任务并行同时进行由于软件运行GC 操作同时进行
因此遇到增量回收相同问题为了解决这个问题并行回收需要屏障当前
状态信息保持更新不过 GC 操作完全并行一点影响原有程序运行做不到
因此 GC 操作某些特定阶段还是需要暂停原有程序运行
快速发展现在并行回收成了非常重要的话算法不断进行
改善硬件系统支持无需中断原有程序完全并行回收已经呼之欲出今后
领域相当值得期待
GC
统一理论
标记清除复制收集这样开始进行扫描判断对象生死算法称为跟踪回收
(Tracing GC )。
相对引用计数法则对象之间引用关系发生变化通过引用计数
进行更新判定对象生死
美国 IBM 公司研究中心 David F. Bacon 2004 发表垃圾回收
统一理论”(A Unified Theory of Garbage Collection )论文阐述一种理论任何
一种 GC 算法跟踪回收引用计数回收思路组合两者关系正如物质
物质一样相互对立其中一方进行改善技术之中必然存在另一进行改善技术
结果只是两者组合而已
69
----------------------- Page 83-----------------------
2 编程语言过去现在未来
例如用于改善回收增量回收跟踪回收算法屏障机制引用状态变化
记录这个角度就是吸收引用计数回收思路相对引用计数算法吸收
回收算法思路进行一些改进来自局部变量引用变化改变引用计数
Unified Theory
来源于物理学中的统一理论(Grand Unified Theory ,简称 GUT )
试图统一解释自然界基本作用力统一理论一样这个试图统一解释跟踪回收
计数回收理论名为 GC 统一理论
70
----------------------- Page 84-----------------------
2.5
异常处理
知道大家有没有听说正常化偏见”(normalcy bias )这个所谓正常化偏见
人们一种心理倾向对于一些偶然发生情况一旦发生便不自觉忽略危害
之前发生地震虽然发布海啸预警据说还是由于觉得
没什么大不了”、“海啸不会袭击这里不幸遇难认为不是那些愚蠢
而是说明人类容易受到正常化偏见这种心理倾向影响如果遇到同样状况
的话可能做出同样错误判断
一定没问题
程序员同样无法逃脱正常化偏见影响对于程序运行发生异常情况
觉得这种情况一般不会出现”、“所以解决没关系”。例如大家可能这样:“
文件肯定安装进去因此不必考虑配置文件存在情况”,“网络通信丢包之类
问题 TCP 帮忙搞定因此应该不用考虑通信失败情况”。总是情况方面设想
这样心理程序员常见
然而正如定律便是极少发生情况只要发生可能性早晚
发生说不定有人小心配置文件手动除了说不定网络通信过程中路
计划永远赶不上变化一旦发生异常情况自己平时对了。“哎呀早知
当初好好应对”,现在意识一点只能马后炮
软件开发历史就是 bug 斗争历史 bug 由于臭虫(bug )组成
继电器引发开发软件过程几乎不会有人想到虫子电路里面
真是意外不过软件开发还是必须各种事态做出预计
这里 2011 3 11 发生日本东北地方太平洋近海地震震级达到9.0
定律(Murphy s Law ),原来表述凡是可能出错都会出错”(Anything that can go wrong will go ’
wrong ),
意思任何事件只要发生概率大于不能假设不会发生
71
----------------------- Page 85-----------------------
2 编程语言过去现在未来
特殊返回表示错误
那么作为例题我们非常简单 # include <stdio.h>
打开文件操作例子 C 语言文件
int
方式打开程序 1 。 main()
{
C
语言 打开文件函数 fopen ,位于指定 FILE f = fopen("/path/to/file", *
"r");
路径文件指定模式 / / 追加打开
打开成功 指向 FILE 结构指针 if (f == NULL) {
puts("file open failed");
失败返回 NULL 。 }
else {
fopen NULL 原因全部 puts("file open succeeded");
}
出来实在下面几个代表性例子: }
1 C 语言中的文件打开操作
‰
文件存在
‰
没有权限访问文件
‰
进程打开文件数量
‰
指定路径不是文件而是目录
‰
内核内存不足
‰
磁盘
‰
指定非法路径地址
上面这些只不过失败原因一部分而已感觉头大
C 语言表示错误主要方式通过特殊返回”。大多数情况 fopen 一样
通过返回 NULL 表示错误
容易忽略错误处理
使用特殊返回这个方法需要编程语言支持一种非常简便方法
缺点
第一由于错误检测不是强制进行可能出现没有注意发生错误继续运行
程序情况如果没有注意文件打开失败依然访问 FILE 结构的话整个程序出错
崩溃仅仅因为打开文件存在崩溃程序实在差劲
72
----------------------- Page 86-----------------------
2.5 
异常处理
对于文件存在这种比较常见状况一般来说大概不会疏于应对不过发生概率比较
意外情况容易忽略例如分配内存函数 malloc ,在内不足返回NULL
表示错误一点文档清楚还是程序没有做出应对如果
NULL malloc 函数连接上去的话惊奇发现居然那么程序根本检查
malloc
返回
第二原本程序容易错误处理埋没错误处理意外异常事态应对
不是我们本来然而正如之前我们不能忽略错误存在于是本来只是
配角错误处理部分程序喧宾夺主
执行一系列简单操作打开文件文件逐行读取内容加工之后输出
输出设备(stdout )。实际代码变成十分繁琐内容打开文件……打开
的话显示错误信息然后程序结束读取 1 内容……读取成功成功的话如果到了
文件末尾程序结束如果文件末尾忽略读取内容进行加工然后输出结果
加工过程如果发生错误错误进行处理……
有没有觉得麻烦这种感觉正常不过受过良好训练 C 语言程序员不会
任何怨言因为他们多年以来一直重复这样辛苦工作
Ruby
中的异常处理
那么对于这样错误地狱”,编程语言方面提供怎样支持
正如上面总结其实问题没有检查错误继续运行错误处理原本
埋没
于是比较语言采用称为异常(exception )机制减轻错误处理负担
一旦发生意外情况程序产生异常同时中断程序运行回溯当前过程调用
经过回溯到达程序顶层之后输出错误信息停止程序运行不过如果明确声明
这里捕获异常的话异常被捕进行错误处理
2 1 程序执行操作 Ruby
f = open("/path/to/file", "r")
编写程序调用用来打开文件 open 方法 puts("file open succeeded")
返回 File 对象如果发生错误,open 2 Ruby 中的文件打开操作
执行中断这里我们没有捕获异常
声明因此产生异常不会被捕程序显示错误信息终止运行异常事态发生
73
----------------------- Page 87-----------------------
2 编程语言过去现在未来
运行终止错误信息输出自动完成
begin
这样一来程序便可以集中完成本职工作。 f = open("/path/to/file", "r")
puts("file open succeeded")
rescue
Ruby 异常捕捉使用 begin 语句 puts("file open failed")
完成。begin 包围代码如果产生异常 end
执行 rescue 部分代码 3 )。 3 Ruby 中的异常处理
由于有了这样异常处理机制 C 语言流派中的错误检查具有问题得到
一定缓解也就是说意外状况发生通过自动中断程序运行方式避免进行
操作检查错误从而避免程序充满错误检查代码问题
不过产生异常不能总是程序结束运行声明需要进行错误处理可以
恢复产生错误程序继续运行
产生异常
下面我们看看如何人为产生异常产生异常可以使用 raise 方法 Ruby ,raise
不是保留而是方法。raise 方法调用创建用来表示异常对象
程序运行这个过程如果存在异常对象匹配 rescue 代码跳转进行异常
处理
raise
方法调用好几方式可以根据状况选择合适调用方式首先基本方式
指定错误信息
raise "something bad happens"
语句产生 RuntimeError 异常如果不在意异常类型只要表达错误信息
可以的话这种方式没有问题
下面这种方式同时指定异常错误信息
raise TypeError, "wrong type given"
这里指定 Exception 个子作为异常。raise 内部创建指定实例
中断当前程序运行 2 方式还有可选 3 参数这个参数可以传递个数
用于保存回溯(backtrace ,哪个函数进行调用信息
如果 rescue 部分重新产生异常可以 raise 方法指定异常对象
74
----------------------- Page 88-----------------------
2.5 
异常处理
raise exc
这种方式包含回溯在内异常信息存在对象从而可以异常位于
上层代码进行处理
还有最后一种方式即可省略所有参数直接调用 raise 方法如果 rescue 中用这种
方式进行调用的话重新产生最近产生异常如果 rescue 外面的话产生
错误信息 RuntimeError 。
高级异常处理
用于异常处理 rescue ,捕获begin 包围区域产生异常这个范围可能
产生异常往往不止一种通过 rescue 后面指定异常种类),可以针对不同种类
分别做出不同应对 4 )。详细异常信息可以通过“=>”后面指定变量获取
产生异常应对方法原则上分为一种中断运行由于异常产生跳转
rescue ,
因此可以中断运行异常处理默认方式
当然有些情况我们并不希望整个程序停止运行例如编辑器读取文件
即便指定文件存在不能弹出错误
begin
信息退出这种情况应该通过异常处理
f = open("/path/to/file", "r")
程序弹出警告对话框然后返回并重接受 puts("file open succeeded")
rescue Errno::ENOENT => e
输入其实中断运行变种。 puts("file open failed by ENOENT")
rescue ArgumentError => e
一种应对方法 消除产生异常原因 puts("file open failed by
ArgumentError")
并重为此,Ruby retry 语句 end
rescue
调用 retry 的话转回相应 begin 4 多个异常处理
重新运行
begin
5 中的程序就是应用 retry 例子。 f = open("/tmp/foo/file", "w")
puts("file open succeeded")
我们 open 方法模式打开名为 / rescue Errno::ENOENT => e
tmp/foo/file
,/tmp/foo puts("file open failed by ENOENT")
Dir.mkdir("/tmp/foo")
存在的话产生异常于是 rescue , retry
我们 mkdir 创建目录然后执行 retry 。 rescue ArgumentError
puts("file open failed by
一来程序返回 begin 部分重新运行 ArgumentError")
end
open 可以成功打开文件
5 调用 retry 进行重试
75
----------------------- Page 89-----------------------
2 编程语言过去现在未来
通过 retry 可以异常处理实现重试操作非常 begin
方便不过缺点就是如果 retry 之前 可能产生异常处理#
rescue
仔细检查是否产生异常条件进行充分应对的话异常处理程序输出消息#
有可能陷入循环。 puts "exception happened"
重新产生异常#
raise
异常处理完成之后有时需要转移上层异常 end
处理程序进一步处理刚才已经 rescue 直接 6 重新产生异常
调用 raise 重新产生异常 6 )。例如如果
显示顶层错误信息的话可以使用这种方式
Ruby
中的处理保证
rescue
用来产生异常时候进行错误处理除此之外还有一种方式可以执行
无论是否产生异常需要进行一些清理工作
打开文件操作为例处理完成无论正常结束还是产生异常必须
关闭
Ruby 使用 open 方法可以保证打开
open("/path/to/file", "r") do |f|
文件进行关闭操作 7 )。如果调用open f处理#
end
方法附加代码代码执行完毕
自动关闭文件 7 代码 open
那么这样机制如果自己实现的话如何
Ruby 使 ensure。 begin def open_close(path, mode, &block)
如果指定 ensure ,begin 部分执行完毕 f = open(path, mode)
begin
必定执行 ensure 部分这里执行 block.call(f)
完毕”,包括执行代码末端正常结束情况, ensure
f.close
包括产生异常或者通过 break、return 中途 end
end
跳出情况只要使用 ensure ,可以实现
代码 open 调用同样功能 8 )。 8 ensure 必定执行
ensure
起源来自 Lisp unwind-protect 函数这个函数意思访问磁带设备
出错防止(protect )出现磁带没有(unwind )情况
76
----------------------- Page 90-----------------------
2.5 
异常处理
其他语言中的异常处理
刚才我们 Ruby 中的异常处理当然其他语言具备异常处理功能例如Java
对应关系这样
begin → try
rescue → catch
ensure → finally
C++ try catch 上面相同不过没有 finally 。 C++ 可以通过对象
函数函数结束必定调用实现相当于 ensure 功能
Java
检查异常
Java
异常处理具有其他语言
void open_file() throws
具备特性方法需要 FileNotFoundException {
return new FileReader("/path/to/file");
声明自己可能产生什么样类型异常。 }
9 Java 中的方法定义节选)。
9 Java 方法定义异常
类型方法参数之后
throws 异常代码用于声明可能产生异常
并且 Java 调用方法对于方法定义声明异常如果没有异常
进行捕获没有 throws 继续上层的话产生编译错误因为异常已经
方法数据类型一部分这样异常称为检查异常(checked exception )。广泛
使用编程语言,Java 应该第一采用检查异常语言
检查异常可以编译器遗漏捕获异常进行检查这个角度这个功能相当有用
符合 Java 一贯策略正如 Java 采用静态数据类型主动规避类型匹配思路
一样
不过检查异常到了一些批判异常之所以称为异常本来因为事先
料到明知如此非要代码强制性地事先异常声明避免产生编译错误
痛苦
有些情况,Java 方法抛出 SQLException IOException 这样异常尽管实际
这些错误数据库文件没什么关系显然由于实现这些功能调用方法
这些异常这些实现详细信息展现用户完全没有必要
77
----------------------- Page 91-----------------------
2 编程语言过去现在未来
尽管如此如果每次一定要按照方法含义更换异常类型或者为了避免编译器
硬着头皮代码捕获异常这就显得本末倒置数据类型问题一样碰到编译
错误也就是编译器惹毛”。如果说因为真正程序错误惹毛编译器就算要是
仅仅 因为异常类型稍稍不合大发雷霆的话这个编译器神经过敏而且如果
为了迁就编译器非要编写异常处理代码的话异常本身便利性全都白费
话说大家千万误会检查异常优点只不过个人比起
严格错误零容忍老师一样编译器还是喜欢 Ruby 这样相对比较宽容
语言
Icon
异常真假
异常比较特别用法为此我们介绍一种叫做 Icon 语言。Icon 美国亚利桑那
大学开发用于字符串模板匹配处理编程语言诞生 1977 一种非常古老语言
Icon 异常 Icon 称为失败通过表示也就是说对表
如果没有产生异常结果反之结果因此
if
表达式
这样条件判断不是 Ruby 一般语言表达式结果判断方式而是表达式
求值成功没有产生异常)”意思也就是说
a < b
这样简单表达式一般语言判断方式 a b 进行比较 b 较大
两者相等 b 小时 Icon 判断方式 a b 进行比较两者相等 b
小时产生异常否则返回 b 因此 Icon
a < b < c
这样表达式比较正当这个表达式进行求值由于 a < b 比较结果表达式
求值结果 b ,接下来 b < c 进行求值如果开始比较结果整个表达式
求值失败后面比较操作实际上没有执行这种方式真的非常独特
Ruby 真假求值语言得到相同结果必须这样
a < b && b < c
Python 其实可以
78
----------------------- Page 92-----------------------
2.5 
异常处理
a < b < c
这样不过不是 Python 具备 Icon 这样运行模块只是语法分析器可以识别
比较运算最终还是表达式转换
a < b && b < c
这样形式
异常基础 Icon 文件逐行读取内容输出程序下面这样
while write(read())
好像语序有点奇怪? Icon 就是这样
首先,read 函数标准输入读取 1 数据读取成功返回读取字符串。write
通过参数得到字符串标准输出通过这样方式完成读取一行内容输出
操作
读懂程序关键在于这个读取一行操作作为 while 循环条件判断使用。Icon
while 语句逻辑执行循环直到条件判断表达式失败”,因此,write(read()) 这个操作
循环执行直到失败为止读取文件末尾,read 函数失败这个失败 while
条件判断捕获从而结束循环习惯一般语言可能感觉异样因为这个 while
循环没有循环可以执行所需操作不过明白其中逻辑觉得顺理成章
此外 Icon 还有一种叫做 every 控制结构可以所有组合进行尝试直到
失败为止。Icon 这种求值方式由于包含继续求值达到某种目标为止含义因此
称为目标导向求值(Goal-directed evaluation )。例如
every write((1 to 3) + (2 to 3))
表示 1 3 2 3 不同排列组合输出它们 1+2、1+3、2+2 、
2+3 、3+2、3+3 ,
运行结果
3
4
4
5
5
6
一般语言这样运算需要通过循环完成运用异常目标导向求值可以
循环情况排列组合运算进行描述一点实在有意思
79
----------------------- Page 93-----------------------
2 编程语言过去现在未来
综上所述 Icon 异常真假组合非常强大应用范围广颇具魅力
设计 Ruby 时候曾经认真思考到底不要采用 Icon 这样真假求值机制结果
还是采用 nil false 表示”,其余表示这样正统方式当时如果做出
一种不同判断的话也许 Ruby 这个语言性质发生改变
Eiffel
Design by Contract
异常这个角度还有一种有意思语言叫做 Eiffel ① 。Eiffiel 强调一种称为
Design by Contract (
契约设计简称DbC )概念所有方法 Eiffel 称为子程序
必须规定执行需要满足条件执行需要满足条件条件不能满足产生异常
这样思路就是对子程序调用看作一种
-- Eiffel
“--”开头注释
兑现满足先验条件约定条件必定得到满足” -- 方法定义
command is
契约。 require
--
先验条件
Eiffel
子程序定义代码 10 。Eiffel 异常 local
--
局部变量声明
没有类型区别强调 DbC 设计方针结果 do
其他语言有所不同。 -- 子程序正文
ensure
--
条件
大家应该可以看出,Eiffel 异常处理使用 rescue
(ensure、rescue、retry ),Ruby 到了继承 -- 异常处理
--
通过 retry返回do重新执行
含义可能有所不同 Ruby 开发早期确实参考 end
Eiffel
中的保留 10 Eiffel 方法定义
异常错误
C 语言这样完全不支错误处理语言异常状况只能通过错误表示那么
具备异常功能语言是不是所有错误可以通过异常表示
一己大部分情况应该使用异常不过有一些情况错误例如
Ruby 有一些情况需要错误
Hash 访问算是例子 Ruby 访问 Hash 如果 key 存在的话并不
异常而是返回 nil (Python 产生异常)。
① Eiffel
一种面向对象编程语言诞生 1986 设计者 Bertrand Meyer (1950— )。
80
----------------------- Page 94-----------------------
2.5 
异常处理
hash[key] =>
存在返回nil#
也就是说要看访问 Hash key 存在情况到底做出程度预计如果
key
存在情况完全超出预计错误应该作为异常处理反之如果 key 存在
某种程度预计范围那么应该返回错误
不过某些情况我们希望 key 存在情况作为错误产生异常并且保证
捕获这种情况可以使用 Hash fetch 方法这个方法的话 key 存在
产生异常
小结
对于程序员错误处理虽然希望发生不能忽视麻烦事情异常
功能就是为了程序员进行错误处理负担尽量减轻产生一种机制。21 世纪编程语言
绝大部分具备异常处理功能编程语言实现进化证据
81
----------------------- Page 95-----------------------
2 编程语言过去现在未来
2.6
闭包
有一次参加叫做“Ruby 集训活动那是学习 Ruby 年轻人参加
历时 5 4 Ruby 编程学习活动参加者一次非常宝贵经验 1 入门培训
2 Ruby 系统学习一遍然后 3 4 分组各自制作相当规模游戏最后
进行展示 一次十分军事化集训活动现场大概不过那些
难度课题发起挑战年轻人还是留下深刻印象
集训活动参加者:“闭包什么?”担任讲师学生不过
没有做出准确理解因此这个机会仔细大家关于闭包的话
函数对象
有一些编程语言提供函数对象一概知道有些人这个叫做闭包(Closure ),
其实这种理解不准确因为函数对象不一定闭包不过话说回来理解闭包首先
理解函数对象那么我们函数对象开始
所谓函数对象顾名思义就是作为对象使用函数不过这里对象不一定面向
对象所指那个对象编程语言操作数据这个意思
例如,C 语言我们可以获取
1 include <stdio.h>#
函数指针通过指针间接 2 int two(int x) {return x 2;}*
函数就是 C 语言概念中的 3 int three(int x) {return x 3;}*
4
1 )。 5 int main(int argc, char **argv)
6 {
一般 C 语言程序员应该大会 7 int ( times)(int);*
8 int n = 2;
函数指针因此我们还是讲解 9
10 if (argc == 1) times = two;
。 11 else times = three;
12 printf("times(%d) = %d\n", n, times(n));
7 ,main 函数 开头 13 }
常见写法 1 C 语言函数对象
82
----------------------- Page 96-----------------------
2.6 
闭包
int ( times)(int);*
指针变量 times 声明意思变量 times ,指向拥有 int 参数
返回 int 函数指针
10 开始 if 语句意思传递程序命令行参数参数
函数 two (指针赋值变量times ;存在以上参数函数 three 指针赋值
times。
综上所述程序没有命令行参数输出
times(2) = 4
命令行参数输出
times(2) = 6
这里大家应该 C 语言中的函数指针有所了解
高阶函数
重要这种函数对象我们编程什么如果什么没有的话只能
设计一种玩具罢了
函数对象也就是函数作为利用方法用途就是高阶函数所谓高阶函数
就是函数作为参数函数这样大家可能明白我们通过例子看一看
我们设想对数进行排序函数这个函数 C 语言编写 API 设计
下面这样
void sort(int a, size_t size);*
函数接受大小 size 整数数组 a ,内容进行排序
不过这个 sort 函数缺点第一只能整数数组进行排序第二排序条件
外部进行指定例如我们希望整数数组进行逆序排序或者希望字符串数组
abc 顺序辞典顺序进行排序等等这个函数无法做到也就是说这个 sort 函数
通用性
83
----------------------- Page 97-----------------------
2 编程语言过去现在未来
函数参数提高通用性
另一方面 C 语言标准
void qsort(void base, size_t nmemb, size_t size,*
提供具有通用性 int ( compar)(const void , const void ));* * *
排序函数名字 qsort ,
2 qsort 函数
API
定义 2
那么这个通用排序函数 qsort 如何克服上述缺点秘密隐藏 qsort 函数
参数
首先我们看看 1参数 base ,类型void* 。sort 1参数限定整数数组
相比之下,qsort 参数表示可以接受任何类型数组这样避免对数类型限制
接下来 2 、 3 参数表示数组大小 sort 传递数组大小元素数量),
qsort 中的2 参数 nmemb 表示元素数量 3 参数 size 表示元素大小这样一来
对于只能整数数组进行排序 sort 函数,qsort 可以任何数据类型数组进行排序
不过还有重要
# include <stdio.h>
就是如何任意类型 # include <stdlib.h>
中的元素进行比较 int icmp(const void a, const void b)
* *
这个问题 qsort {
int x = (int )a;* *
4 参数 compar 。 int y = (int )b;
* *
compar
指向 if (x == y) return 0;
if (x > y) return -1;
参数函数指针这个函数 return 1;
接受数组元素指针, }
整数形式返回比较 int main(int argc, char argv)**
{
元素相等返回 0 , int ary[] = {4,7,1,2};
a b 返回正整数 const size_t alen = sizeof(ary)/sizeof(int);
size_t i;
a
b 小时返回负整数
for (i=0; i<alen; i++) {
qsort
printf("ary[%d] = %d\n", i, ary[i]);
}
3
这里我们定义 qsort(ary, alen, sizeof(int), icmp);
for (i=0; i<alen; i++) {
icmp 函数可以 printf("ary[%d] = %d\n", i, ary[i]);
整数进行逆序比较 , }
}
qsort
函数数组中的元素
3 qsort 函数应用实例
降序排序
84
----------------------- Page 98-----------------------
2.6 
闭包
大家现在应该已经明白,qsort 函数通过另一函数作为参数使用实现通用排序
功能高阶函数这样方式通过一部分处理函数对象形式转移外部从而实现
算法通用
函数指针局限
关于(C 语言函数指针以及用作参数高阶函数强大我们已经
下面我们来讲局限
作为例题我们设想一下结构构成链表(Linked list )遍历处理高阶
进行抽象
4 一般循环高阶函数方式链表进行遍历程序 4 程序由于 C 语言
性质缘故显得本质部分 main 函数 38 开始
39 开始 while 语句没有使用高阶函数而是直接循环实现受过良好训练
C 语言程序员可能觉得没什么不过看懂 41
l = l->next
写法需要具备关于链表内部原理知识其实这些涉及底层部分最好能够隐藏起来
另一方面 43 开始 foreach 函数部分非常清晰简洁只不过受到 C
语言语法制约这个函数必须远离循环地方单独进行定义 C 语言函数指针
缺点大多数语言函数可以需要调用地方当场定义因此这个缺点 C 语言
有的
不过另一重要缺点相比第一缺点简直缺点如果运行这个程序的话
结果下面这样
node(0) = 3
node(1) = 2
node(2) = 1
node(3) = 0
node(?) = 3
node(?) = 2
node(?) = 1
node(?) = 0
前面 4 while 语句输出结果后面 4 foreach 输出结果。while 语句输出结果
85
----------------------- Page 99-----------------------
2 编程语言过去现在未来
可以显示索引 foreach 部分只能显示“?”。因为 while 语句不同,foreach
实际上另一函数执行因此无法函数访问位于外部局部变量 i 。当然如果
i 全局变量存在这个问题不过为了这个目的使用副作用全局变量
不是主意因此,“对外局部变量访问 C 语言函数指针弱点
1 include <stdio.h>#
2 include <stdlib.h>#
3
4 struct node { /
结构定义 /* *
5 struct node next;*
6 int val;
7 };
8
9 typedef void ( func_t)(int); /
函数指针类型 /* * *
10
11 void /
循环函数 /* *
12 foreach(struct node list, func_t func)*
13 {
14 while (list) {
15 func(list->val);
16 list = list->next;
17 }
18 }
19
20 void /
循环主体函数 /* *
21 f(int n)
22 {
23 printf("node(?) = %d\n", n);
24 }
25
26 main() / main
函数 /* *
27 {
28 struct node list = 0, l;* *
29 int i;
30 /
准备开始 /* *
31 for (i=0; i<4; i++) { /
创建链表 /* *
32 l = malloc(sizeof(struct node));
33 l->val = i;
34 l->next = list;
35 list = l;
36 }
37
38 i = 0; l = list; /
例题主体 /* *
39 while (l) { / while
循环 /* *
40 printf("node(%d) = %d\n", i++, l->val);
41 l = l->next;
42 }
43 foreach(list, f); / foreach
循环 /* *
44 }
4 高阶函数循环
86
----------------------- Page 100-----------------------
2.6 
闭包
作用变量可见范围
现在我们已经了解 C 语言提供函数指针缺点于是为了克服这些缺点出现功能
就是主题——闭包
现在大家已经理解函数对象下面我们讲解一下闭包话说讲解闭包必须
使用一种支持闭包语言因此这里我们 JavaScript 讲解肯定有人为什么
Ruby 关于一点我们稍后
首先为了帮助大家理解闭包我们介绍术语作用(Scope )生存周期
(Extent )。
作用变量有效范围也就是变量可以访问范围 JavaScript 保留
var 表示变量声明所在内侧代码就是作用单位 5 ),没有进行声明
变量就是全局变量作用嵌套因此位于内侧代码可以访问身为作用
变量以及以外代码作用变量
另外大家创建匿名函数对象语法 JavaScript 通过下面语法创建函数
对象
function () {...}
5 我们匿名函数赋值变量如果赋值直接作为参数传递可以
当然这个函数对象自己作用
var a = 1; // a
全局变量 a、g 作用
function foo() {
var b = 2; // b
foo可见 b、f 作用
g = 3; // g
全局变量
var f = function () {
var c = 4; // c
函数可见 c 作用
return a+b+c // a、b
c可见
};
f(); //
调用函数对象f
}
5 JavaScript 中的作用
JavaScript 4 foreach
JavaScript
可以更加直接编写出来 4 本质部分 JavaScript 改写程序 6
87
----------------------- Page 101-----------------------
2 编程语言过去现在未来
function foreach(list, func) { //
循环高阶函数
while (list) {
func(list.val);
list = list.next;
}
}
var list = null; //
变量声明
for (var i=0; i<4; i++) { // list
初始化
list = {val: i, next: list};
}
var i = 0; // i
初始化
//
函数对象访问外部变量
foreach(list, function(n){console.log("node("+i+") = "+n);i++;});
6 高阶函数循环
这里值得注意作为 foreach 参数函数对象可以访问外部声明变量 i 结果
C
语言 foreach 函数无法实现索引显示功能这里可以实现因此函数对象
能够对外变量进行访问引用更新),闭包构成要件之一
按照作用思路可能大家觉得上述闭包性质理所当然不过如果我们加上
另外概念——生存周期结果可能出乎意料
生存周期变量存在范围
所谓生存周期就是变量寿命对于表示程序变量可见范围作用生存
这个概念变量可以周期范围存在能够访问清楚这个概念
我们还是看看实例
7 例子返回函数
function extent() {
函数 extent 这个函数 var n = 0; // 局部变量
return function() {
函数对象函数对象 n++; // n访问
extent 中的局部变量 n 进行 console.log("n="+n);
}
显示。 }
f = extent(); //
返回函数对象
那么这个程序实际运行 f(); // n=1
f(); // n=2
如何
7 变量生存周期
extent()
执行返回函数对象
我们赋值变量这个函数变量每次执行局部变量更新从而输出
结果
88
----------------------- Page 102-----------------------
2.6 
闭包
这里觉得有点
局部变量 n extent 函数声明 extent 函数已经执行完毕变量脱离作用
之后不是应该消失不过这个运行结果即便函数执行完毕之后局部
n 貌似地方继续活着
就是生命周期也就是说这个属于外部作用中的局部变量函数对象封闭
里面闭包(Closure )这个原本就是封闭意思封闭起来变量寿命封闭
函数对象寿命相等也就是说封闭这个变量函数对象不再访问垃圾回收回收
这个变量寿命同时终结
现在大家明白闭包定义 函数对象局部变量环境封闭起来结构
闭包因此,C 语言函数指针不是闭包,JavaScript 函数对象闭包
闭包面向对象
7 程序函数每次执行作为隐藏上下文局部变量 n 引用更新
也就是说意味着函数过程数据结合起来
过程数据结合形容面向对象中的对象经常使用表达对象数据
方法形式内含过程闭包
function extent() {
过程 环境形式内含数据, return {val: 0,
call: function() {
对象闭包同一事物正反两面 this.val++;
同一事物正反两面就是说使用 console.log("val="+this.val);
}};
中的一种方式可以实现一种方式 }
能够实现功能① 。例如 7 程序, f = extent(); // 返回对象
f.call(); // val=1
如果 JavaScript 面向对象功能 f.call(); // val=2
的话成了 8 中的样子 8 通过面向对象实现
Ruby
函数对象
到此为止我们例子使用语言 JavaScript ,为什么不用擅长 Ruby 语言
下面说说理由
准确对象可以拥有多个过程闭包只能拥有但是闭包可以相当于过程符号作为参数
进行传递通过内部分支实际上可以提供过程功能。(
89
----------------------- Page 103-----------------------
2 编程语言过去现在未来
理由,Ruby 语言没有函数这个概念作为纯粹面向对象语言,Ruby
中的一切过程属于对象方法并不存在独立对象之外函数但是,Ruby 具备
函数对象相同功能 Proc (过程对象实际应用函数对象用法差不多的不过
这样一来讲解变得麻烦因此我们便采用具备简单函数对象功能 JavaScript 。
大家演示一下 Ruby def extent
实现 JavaScript 相同 n = 0 局部变量#
lambda {
过程对象表达式#
7 Ruby n+=1 n访问#
改写一下 9 。 printf "n=%d\n", n
}
end
7 9 对比一下 f = extent(); 返回函数对象#
注意 Ruby 创建 f.call(); n = 1#
f.call(); n = 2#
对象需要使用 lambda{ …} 表达
9 Ruby 变量生存周期
调用过程对象不能加上
一对括号而是必须通过 call 方法进行调用
Ruby 1.9 函数编程提供支持,lambda 可以 -> 表达式替代此外 call
方法调用可以省略 f.() 形式只不过 f 后面那个圆点必须一点遗憾
Ruby
JavaScript 区别
函数这个角度,Ruby JavaScript 区别还是关于一点我们详细说说
正如之前,Ruby 只有方法没有函数过程对象可以类似函数方式
使用由于过程对象不是函数因此需要调用 call 方法除此之外闭包其他语言
函数对象具备性质过程对象具备另一方面,JavaScript 函数自然可以作为
对象引用 7 )。但是,JavaScript 方法函数区别模糊同样函数作为通常
函数调用作为对象方法调用,this 发生变化 10 )。
f = function() {
console.log(this);
}
#
直接调用f
f(); this
global上下文#
obj = {foo: f};
f变为方法#
#
f作为方法调用
obj.foo(); this
obj#
10 JavaScript this
90
----------------------- Page 104-----------------------
2.6 
闭包
Lisp-1
Lisp-2
Ruby
JavaScript 区别还有一点就是访问方法成员行为方式例如假设 Ruby
JavaScript 程序中都名为 obj 对象两者拥有名为 m 方法这时同样
访问
obj.m
Ruby
JavaScript 行为差异 Ruby 代码表示 m 方法进行
调用 JavaScript 表示返回实现 m 方法函数对象如果进行参数调用的话
括号不能省略
obj.m()
也就是说,JavaScript 圆点引导访问代表属性引用函数作为属性返回
就是方法加上括号可以进行调用
另一方面,Ruby 圆点引导访问只不过方法调用而已不加括号影响
方法调用行为 Ruby 如果获取实现方法过程对象需要使用 method 方法
1 )。
1 RubyJavaScript方法访问
Ruby JavaScript
方法调用参数) obj.m obj.m()
方法调用参数) obj.m(1) obj.m(1)
方法获取 obj.method(:m) obj.m
一种 JavaScript 整体比较简洁印象实际上,JavaScript
获取方法实现不会频繁执行操作反而赋予一种简短记法无法 Ruby 一样
方法调用括号因此难说 JavaScript 这种模式一定比较当然这里
私心)。
整体作为纯粹面向对象语言,Ruby 方法调用中心位置相对而言
JavaScript
面向对象功能函数对象概念发展
Python
采用 JavaScript 手法如果一种原本并非面向对象设计语言
面向对象功能的话一种十分有效手法
类似这样设计思想差异 Lisp 早就存在做法分别叫做 Lisp-1 (JavaScript
风格 Lisp-2 (Ruby 风格)。
91
----------------------- Page 105-----------------------
2 编程语言过去现在未来
Lisp 方言,Scheme 属于 Lisp-1 函数变量命名空间相同。Lisp-1
名称貌似就是命名空间唯一概念 Scheme 函数就是存放函数
引用变量而已
因此可以这样
(display "hello world")
(define d display)
(d "hello world")
通过赋值操作可以函数定义别名
此外,Lisp 另一方言 EmacsLisp 变量函数分别拥有各自命名空间如果执行
这样赋值操作
(echo "hello world")
(setq e echo)
产生错误
undefined variable echo
由于虽然存在名为 echo 函数存在名为 echo 变量如果 EmacsLisp
实现上述 Scheme 例子相同操作需要这样
(fset 'e (symbol-function 'ech
o))
(e "foo")
symbol-function
用来通过名称获取函数实体 fset 名称函数实体进行关联虽然
Scheme
风格看上去比较简单获取函数实体这种操作一般人不会因此没有
这种操作定义这么简单当然 Lisp 本来不是一般人一点我们

现在大家应该明白 闭包可以实现更加高度抽象刚才我们介绍 C、
JavaScript 、Ruby 、Lisp
各种语言函数对象实现手法希望大家能够通过上面介绍
这些语言设计者设计语言思路大致理解
92
----------------------- Page 106-----------------------
编程语言过去现在未来后记
正文未来编程语言进行预测认为云计算支持编程语言
未来发展趋势作为计算进化方向多个计算机核心协同工作一点认为
毫无疑问环境编程 6 时代编程”),以及服务器
计算机编程 4 云计算时代编程”)话题进行阐述
然而谈到编程语言进化方向老实说有点雾里看花感觉今后到底
一种云计算设计进行积极支持语言然后这种语言逐步流行起来
还是现存语言基础形式不断添加上述环境支持虽然自诩编程
方面专家对我来说依然预测的话
例如,Erlang 一种并行分散编程提供积极支持语言瑞典爱立信公司
20
世纪 80 年代后半开始开发这种语言风格
‰
Prolog 影响
‰
动态函数语言
‰
单一赋值循环
‰
基于 Actor 消息传递
‰
容错
以往语言差别近来发展趋势迅速走红此外无需指定
能够内部实现并行计算可能性 Haskell 语言值得关注
但是尽管 Erlang Haskell 获得广泛关注当前云计算发展速度相比
它们走红只是一时其中原因可能因为现有语言增加一些功能足够
需要全新语言可能因为 Erlang Haskell 提供以往不同范式
模型一般程序员无法适应总之现在这个时点做出判断
因此这个领域今后还是非常值得关注
93
----------------------- Page 107-----------------------
2.6 
闭包
编程语言潮流 3
95
----------------------- Page 108-----------------------
3.1
语言设计
接下来我们语言设计角度, 比较一下 客户端 服务器
Java 、JavaScript 、Ruby
Go 4 语言
语言看起来彼此完全不同如果选择合适
标准可以它们非常清楚进行分类 1
动态
运行时 HTML5 Ruby
决定
JavaScript
客户端语言代表,Java 其实
静态
黎明作为客户端语言活跃时间应该 运行 Java Go
记得 Java Applet 这个名词之后,Java 决定
服务器语言代表地位扶摇直上 1 4 语言分类
Java
时候作为客户端语言诞生
出身这里还是分类客户端语言
另一分类标准就是静态动态所谓静态就是实际运行程序通过程序代码
字面确定结果意思所谓动态就是只有运行时确定结果意思静态动态具体
所指内容多种大体上的话就是运行模式类型 4 语言全都具备面向对象
性质面向对象本身就是一种包含动态概念性质不过语言之中,Java Go
比较偏重静态语言 Ruby JavaScript 比较偏重动态语言
客户端服务器
首先我们这些语言按照客户端和服进行分类前面这种分类是以
语言刚刚出现使用方式基准
现在 Java 用作服务器语言我们分类客户端语言可能
感到有点莫名其妙。Java 确实现在已经用作客户端语言但是我们不能忘记诞生
1995
Java ,正是伴随嵌入浏览器中的Applet 技术出现
Java
虚拟机(VM )作为插件集成浏览器编译Java 程序(Applet )虚拟机
运行这种技术当初为了增强浏览器功能往前追溯的话,Java 原本名叫 Oak ,
97
----------------------- Page 109-----------------------
3 编程语言潮流
面向嵌入设备编程语言诞生因此出身的话,Java 还是一种面向客户端的
编程语言
Java
具备 VM 和平无关字节特性本来就是客户端运行 Applet 目的
各种不同环境能够产生相同行为这样特性对于服务器虽然不能
价值但是服务器环境可以服务提供者自由支配因此至少可以这样特性
带来关键好处另一方面客户端环境操作系统浏览器千差万别因此
平台无关要求一直
Java
诞生互联网黎明时期那个时候浏览器不是电脑必备软件当时主流
浏览器 Mosaic Netscape Navigator ,① 除此之外还有一些其他类似软件 Internet
Explorer
刚刚崭露头角
那个充满梦想时代如果开发一种功能亮点浏览器有可能称霸业界
Sun Microsystems
公司推出 Java 编写浏览器 HotJava ,世界展示 Applet
然而随着浏览器市场格局逐步固定他们转变策略改为主流浏览器提供插件
集成 Java ,从而Applet 运行提供支持
服务器华丽转身
然而,Java 诞生之后客户端方面取得多大成功于是便开始着手进入服务器
领域造成这种局面原因认为其中主要原因应该 Applet 这个平台迟迟
没有出现杀手级应用(killer app )。
处于刚刚诞生之际 Java 遭到 批判体积臃肿运行缓慢不同浏览器
Java
插件之间存在一些兼容性方面问题使得 Applet 应用没有真正流行起来这个
,JavaScript 作为客户端编程语言更加实用获得越来越关注当然那个
Java 已经完全确立自己作为服务器编程语言地位因此丧失客户端领地不至于
感到特别
Java
客户端服务器端的转身可以相当成功与此同时,Sun Microsystems
① Mosaic
世界第一真正流行互联网浏览器软件美国国家超级计算机应用中心(National Center for
Supercomputing Applications ,NCSA )
开发,1993 发布, 1997 停止开发。Netscape Navigator 网景公司
(Netscape )
开发互联网浏览器软件,1994 发布曾经一度市场占有率最高浏览器软件后来
地位微软 Internet Explorer 取代
② Sun Microsystems
2010 74 亿美元 Oracle 收购
98
----------------------- Page 110-----------------------
3.1 
语言设计
IBM
公司着手 JVM (Java VM )进行改良使得性能到了改善某些情况性能
超越 C++。之前 Java 性能情形现在 Java 这样性能人气简直
做梦一样
服务器获得成功四大理由
由于本人没有大规模实践 Java 编程因此对于 Java 服务器取得成功来龙去脉
说真的不是了解不过如果想象一下的话大概下面几个主要因素
1.
可移植性
虽然服务器环境客户端环境更加服务器环境使用系统平台种类相当
Linux 、Solaris、FreeBSD 、Windows 根据需要可能系统上线之后更换系统平台
这样情况,Java 具备一次编写到处运行特性显得魅力十足
2.
功能强大
Java
服务器崭露头角 20 世纪 90 年代那个时候状况 Java 比较有利
Java
定位比较相似语言静态类型编译面向对象编程语言属于主流
只有 C++ 而已
Java 诞生 20 世纪 90 年代中期正好作为 C++ 程序员开发 CAD 相关系统时候
当时 C++ 处于发展过程实际开发模板异常功能无法真正得到运用
相比之下,Java 开始具备垃圾回收(GC )机制语言内置异常处理
标准完全运用异常处理设计程序员简直天堂毫无疑问,Java 语言
这些优秀特性帮助确立服务器编程语言地位功臣之一
3.
高性能
Java
为了实现一次编写到处运行宣传口号不是程序直接转换系统平台
对应机器语言而是转换虚拟 CPU 机器语言字节”(Bytecode ),通过搭载虚拟
CPU
模拟器 JVM 运行。JVM 归根到底其实运行时用来解释字节解释器理论
运行速度应该无法直接生成机器语言原生编译器媲美
事实上 Java 诞生初期确实没有达到编译语言有的运行速度当时用户经常
Java 这样令人印象深刻
99
----------------------- Page 111-----------------------
3 编程语言潮流
然而技术革新伟大随着各种技术进步现在 Java 性能已经能够堪称顶级
例如一种叫做 JIT (Just In Time )编译技术可以运行时字节转换机器语言
经过转换之后可以获得原生编译一样运行速度运行时进行编译意味着编译
包含运行时间里面因此优秀 JIT 编译器通过侦测运行信息需要频繁
瓶颈部分进行编译从而大大削减编译所需时间而且利用运行时编译可以不用
连接问题积极运用扩展① ,因此某些情况运行速度甚至可以超过 C++。
Java 性能提高另一障碍就是 GC。GC 需要对象进行扫描不用对象
回收这个过程程序本身进行操作无关换句话说就是用功因此消耗

时间拖累 Java 程序 性能作为对策最新 JVM 采用并行回收回收
技术
4.
丰富
随着 Java 人气应用逐渐广泛,Java 能够使用越来越增加提高开发
效率从而反过来拉高 Java 人气形成良性循环现在 Java 人气已经无可撼动
客户端的 JavaScript
Applet
客户端扩展浏览器功能做出尝试然而并不成功浏览器画面中的
矩形区域运行应用程序 Applet ,没有作为应用程序发布手段流行起来
几乎同一时期出现 JavaScript ,一种集成浏览器中的语言但是可以一般
网页嵌入程序逻辑一点 Java Applet 完全不同方式最终获得成功
JavaScript
Netscape Communications 公司开发通过 JavaScript ,用户点击网页
链接按钮不光可以进行页面跳转可以改写页面内容这样功能十分便利
因此 Netscape Navigator 之外浏览器成了 JavaScript 。
随着浏览器不断竞争淘汰主流浏览器全部支持 JavaScript 情况便发生
Google 这样产品整体框架 HTML 组成实际显示部分却是通过
扩展(Inline expansion )编译器直接完整函数插入调用函数地方从而提高
调用运行速度
并行回收可以 GC 单独线程运行从而程序本身处理基本上造成影响回收
过程忽略程序运行一直存活长寿对象从而减少扫描工作量降低 GC 开销技术。(
网景通信(Netscape Communications ) 1998 美国在线(AOL )收购2003 解散
100
----------------------- Page 112-----------------------
3.1 
语言设计
JavaScript
服务器获取数据显示出来这样手法从此开始流行起来
JavaScript 服务器进行异步通信 API 叫做 XMLHttpRequest ,因此衍生
手法便称为 Ajax (Asynchronous JavaScript and XML ,异步 JavaScript XML )。国有
叫做 Ajax 厨房清洁剂说不定那个名字模仿
性能显著提升
目前客户端编程语言JavaScript 成为强有力竞争者随着 JavaScript 重要性
不断提高 JavaScript 引擎投资不断增加使 JavaScript 性能到了显著改善改善
JavaScript
性能主要技术除了 Java 相同 JIT GC 之外还有特殊(Specialization )技术
Java ,JavaScript 一种动态语言带有变量表达式类型信息针对类型
优化非常困难因此性能静态语言相比有着先天劣势特殊就是提高动态语言
性能技术之一
我们设想 2 这样 JavaScript 函数这个函数 function fact(n) {
用于阶乘计算大多数情况参数 n 应该整数 if (n == 1) return 1;
return n fact(n-1);*
JIT 需要统计运行时信息因此 JavaScript 解释器知道参数 }
n
大多数情况整数 2 JavaScript 函数
于是解释器 fact 函数进行 JIT 编译生成版本函数 n 任意
通用版本另一假设 n 整数高速版本参数 n 整数大多数情况),
运行那个高速版本函数便实现静态语言几乎相同运行性能

除此之外最新 JavaScript 引擎 进行其他大量优化 JavaScript 目前
动态语言应该并不
JavaScript
客户端称霸之后开始准备服务器进军② 。JavaScript 存在将来
应该越来越
这些优化包括 JavaScript 本来列表(Hash table )形式实现对象进行数组从而提高访问速度
特殊结合实现静态语言同等对象访问速度技术。(
各种服务器 JavaScript 尝试有力一种就是 node.js 。node.js Google Chrome 搭载高速
JavaScript
引擎 v8 ,异步 I/O 结合产物 6 我们介绍 node.js 。(
101
----------------------- Page 113-----------------------
3 编程语言潮流
服务器端的 Ruby
客户端编程问题就是必须要求台客安装相应软件环境 Java
JavaScript
诞生 20 世纪 90 年代后半互联网用户局限于一部分先进用户然而现在
联网已经大大普及用户水平构成跟着变得复杂起来台客安装相应软件
环境大大提高软件部署门槛
相对服务器没有这样制约可以选择适合自己编程语言
Ruby 1993 互联网没有现在这样普及因此 Ruby 不是开始面向
Web
服务器设计然而 WWW 黎明开始为了实现动态页面出现通用网关
(Common Gateway Interface ,CGI )技术Ruby 逐渐这种技术到了应用
所谓 CGI ,通过 Web 服务器标准输入输出程序进行交互从而生成动态 HTML 页面
接口只要可以标准输入输出进行操作那么无论任何语言可以编写 CGI 程序不得
归功 WWW 设计 灵活性使得动态页面可以容易编写出来正是因为如此使得
WWW
逐渐风靡全世界
WWW Web 服务器请求信息是以文本方式传递反过来返回 Web
服务器响应信息是以文本(HTML )方式传递因此擅长文本处理编程语言具有
优势于是脚本语言时代到来以往只是用于文本处理脚本语言应用
便一下子扩大
早期应用 CGI Web 页面大多 Perl 编写作为“Better Perl” Ruby 随之
得到越来越应用
Ruby on Rails
带来飞跃
2004
随着 Ruby on Rails 出现使得 Web 应用程序开发效率大幅提升引发
广泛关注当时已经出现 Web 应用程序框架 Ruby on Rails 可以
Ruby on Rails
特性包括
‰
完全 MVC 架构
‰
使用配置文件尤其是 XML )
‰
坚持简洁表达
‰
积极运用编程
‰
Ruby 核心大胆扩展
102
----------------------- Page 114-----------------------
3.1 
语言设计
基于这些特性,Ruby on Rails 实现开发效率灵活性到了广泛应用可以
Ruby
拥有现在人气基本上 Ruby on Rails 作出贡献
目前作为服务器编程语言,Ruby 人气可谓无可撼动一种说法硅谷中心
Web 创业公司超过一半采用 Ruby 。
不是只要服务器环境,Ruby 一定可以所向披靡规模较大企业
网站运营部门管理服务器安装软件并不容易实际上企业曾经 Ruby
on Rails
开发面向技术人员 SNS ,时间完成搭建但是等到正式上线
时候运营部门这种知道哪个家伙开发经过第三方安全认证 Ruby
之类软件不可以安装我们数据中心主机上面这样理由拒绝安装真是
头疼
不过开发部门工程师没有气馁而是 Java 编写 Ruby 解释器 JRuby ,开发
SNS 转换 jar 文件从而使可以 Sun Microsystems 公司应用程序服务器 GlassFish
运行当然,JVM GlassFish 已经服务器安装好了这样一来运营方面没有理由
拒绝多亏 JRuby ,结局皆大欢喜
JRuby
真是关键时刻大显身手
服务器端的 Go
Go
一种新兴编程语言出身名门著名 UNIX 开发者 ·派克 ·
开发因此到了广泛关注
Go
诞生背景源于 Google 关于编程语言一些问题 Google 公司作为
编程环境公司产品开发使用编程语言限于 C/C++、Java 、Python
JavaScript 。
实际上有人私底下 Ruby ,不过正式产品使用语言上述 4 。②
4 语言使用遵循一定分工客户端语言 JavaScript ,服务器语言脚本
Python ,追求大规模高性能 Java ,文件系统面向平台系统编程 C/C++。这些
语言,Google 公司不满意就是 C/C++
派克(Rob Pike ,1956— )加拿大程序设计师早年贝尔实验室 UNIX 小组成员参与设计
实验室 9 计划(Plan 9 )、Inferno 操作系统 Limbo 编程语言目前就职 Google 公司(Ken
Thompson ,1943— )
美国计算机科学家参与设计 Plan 9 、B 语言、C 语言 1983 获得图灵奖
此外还有一些内部专用语言例如为了用于处理 Web 爬虫抓取大量数据 MapReduce 运行高效
使用专用语言 Sawzall。(
103
----------------------- Page 115-----------------------
3 编程语言潮流
其他一些编程语言相比,C/C++ 历史比较因此具备垃圾回收最近语言
提供编程辅助功能因此由于开发效率一直无法得到提高便产生设计一种
系统编程语言需求能够胜任位置正是全新设计编程语言 Go。
Go
具有特性,(观点比较重要下列几点
‰
垃圾回收
‰
支持并行处理 Goroutine ①
‰ Structural Subtyping (
结构类型
关于最后一点 Structural Subtyping ,我们在后面对类型系统讲解进行说明
静态动态
刚才我们已经 4 语言客户端服务器端的角度进行分类接下来我们动态
静态角度看一看语言
正如刚才所谓静态就是无需实际运行根据程序代码确定结果意思
所谓动态只有到了运行时才能确定结果意思
不过无论任何程序或多或少包含动态特性如果程序完全静态的话
意味着需要代码进行字面分析可以得到所有结果这样一来程序运行
任何意义例如编程计算 6 阶乘如果按照完全静态方式编写的话应该下面
这样
puts "720"
不过除非玩具一样演示程序否则不会开发这样程序实际由于
输入数据或者用户之间交互程序才能每次运行时得到不同要素
因此作为程序实现编程语言多多少少具备动态性质所谓动态还是静态
这种语言对于动态功能进行多少限制或者反过来说动态功能进行多少积极
强化我们探讨其实语言这种设计方针
① Goroutine
Go 有的术语简单就是线程随着处理器普及并发编程重要性
提高然而 C/C++ 语言层面并不支持并发编程在内层面可以使用线程(pthread ),起来没有
那么方便 Go 通过语言层面提供支持支持系统编程层面并发编程有效利用。(

104
----------------------- Page 116-----------------------
3.1 
语言设计
例如这里列举 4 编程语言面向对象语言面向对象语言都会具备
称为多态(Polymorphism )或者动态绑定动态性质根据存放变量中的对象实际性质
自动选择一种合适处理方式方法)。这样功能可以面向对象编程本质
属于动态编程语言动态部分主要运行模式类型两者相互独立概念
采用动态类型语言运行模式具有动态倾向反之一样静态语言运行
模式运行时灵活性受到一定限制
动态运行模式
所谓动态运行模式简单就是运行中的程序能够识别自身自身进行操作
程序自身进行操作编程称为编程① (Metaprogramming )。
Ruby JavaScript 编程十分自然比如查询对象拥有哪些方法或者
运行时方法进行定义等等这些理所当然
另一方面 Java 类似编程手法通过反射 API”实现虽然进行取出
操作功能可以做到并非 Ruby JavaScript 那样感到自由自在而是虽然
做到一般不会这样感觉
Go
一样 Go 通过利用 reflect 可以获取程序运行时信息主要类型),但是
理解范围无法实现进一步编程功能之所以没有采用 Java 进一步动态
运行模式恐怕因为可能系统编程领域必要性不大或者担心运行速度产生
负面影响之类原因
何谓类型
一般性层面类型数据有的性质进行描述例如
怎样可以进行哪些操作等等动态类型立场数据拥有类型只有数据
类型静态类型立场数据拥有类型存放数据变量表达式拥有类型类型
编译固定
程序自身进行操作称为反射”(Reflection )。英语,Reflection 自己进行反省
同样意思 Ruby 大多称为编程 Java 大多称为反射。(
其实类型这个话题现在许多计算机科学论文设计领域算是比较热门不过这里我们
探讨那些尖端的话说实话数学素养实在不行对于那些数学公式类型理论方面论文
实在无法理解因此遗憾没办法讲解这些内容。(
105
----------------------- Page 117-----------------------
3 编程语言潮流
然而便是静态类型由于面向对象语言中的多态特性必须具备动态性质
需要追加规则 实际数据类型),静态指定类型类型所谓类型
(Subtype ),
具有继承关系或者拥有同一接口静态类型数据类型系统拥有
同一性质”。
静态类型优点
动态类型比较简洁灵活性静态类型优点由于编译已经确定
类型因此比较容易发现 bug 。当然程序中的 bug 大多数逻辑有关单纯类型
错误导致 bug 只是少数不过逻辑错误通常随着编译可以检测类型
匹配也就是说通过类型错误可以其他 bug 显露出来
除此之外程序类型描述可以帮助程序阅读理解或者可以成为关于程序
行为参考文档可以优点
此外通过静态类型可以编译获得可以利用信息编译器便可以生成
优质 代码从而提高程序性能然而通过 JIT 技术动态语言可以获得原生编译
语言相近性能说明今后静态语言动态语言之间性能差距继续缩小
动态类型优点
相对而言动态类型优点在于简洁灵活性
极端一点的话类型信息其实程序运行本质无关阶乘计算程序
无论声明类型 Java 编写 3 ),还是声明类型Ruby 编写 4 ),
算法毫无别的然而由于关于类型描述因此 Java 算法本质无关
代码分量增加
class Sample { def fact(n)
private static int fact(int n) { if n == 1
if (n == 1) return 1; 1
return n fact(n - 1);* else
} n fact(n - 1)*
public static void main(String[] argv) { end
System.out.println("6!="+fact(6)); end
} print "6!=", fact(6), "\n"
} ---
3 Java 编写阶乘程序 4 Ruby 编写阶乘程序
106
----------------------- Page 118-----------------------
3.1 
语言设计
而且类型带来制约 3、 4 程序 6 阶乘进行计算
这个数字继续增大,Java 超过 13 阶乘的话无法正确运行 3 程序
fact
方法接受参数类型声明 int Java int 32 可以表示接近 20 亿

整数如果阶乘计算结果超出这个范围导致溢出
当然由于 Java 拥有丰富资源 BigInteger 可以实现上限整数计算
这就需要对上面的程序较大幅度改动由于计算机存在“int 幅度 32 限制
使得阶乘计算灵活性大大降低
另一方面,Ruby 没有这样制约算是计算 13 阶乘甚至 200 阶乘
可以直接计算出来无需担心 int 大小计算机限制问题
其实这里还是有点把戏同样动态语言 1 中的 JavaScript 计算 200 阶乘
输出 Infinity (无穷)。其实 ,JavaScript 数值浮点数因此无法 Ruby 那样支持
计算也就是说要不制约进行计算除了类型性质之外支持非常重要
鸭子就是鸭子
动态语言一种叫做鸭子类型(Duck Typing )风格广泛应用鸭子类型这个称谓
据说下面英语童谣
If it walks like a duck and quacks like a duck, it must be a duck. (
如果鸭子一样走路鸭子
一样呱呱叫一定鸭子
童谣我们可以导出规则如果对象行为鸭子一模一样
真正实体什么我们可以看做鸭子也就是说考虑对象到底
哪一个实例关心拥有怎样行为拥有哪些方法),就是鸭子类型因此
必须排除对象产生分支
编程达人大卫 ·托马斯(Dave Thomas )提出
例如假设存在 log_puts(out, mesg) 这样方法用来 mesg 这个字符串输出 out
输出目标。out 需要指定类似 Ruby 中的 IO 对象或者 Java 中的 OutPutStream 这样
对象这里本来文件输出日志忽然输出内存的话怎么办比如说
日志输出结果合并字符串然后取出
① 13
阶乘结果 6227020800 , Java int 表示范围 -2147483648 2147483647 。
107
----------------------- Page 119-----------------------
3 编程语言潮流
Java 静态语言,out 指定对象必须拥有共同或者接口无法选择
完全无关对象作为输出目标实现这样操作要么开始事先准备这样接口
原来要么准备可以切换输出目标包装对象(wrapper object )。无论如何
没有事先预计需要输出内存的话需要程序进行大幅改动
如果采用鸭子类型风格动态语言容易产生这样问题只要准备 IO
具有同样行为对象指定 out 的话即便不对程序进行改动,log_puts 方法能够
执行可能性相当实际上 Ruby 确实存在 IO 毫无继承关系 IO
同样行为 StringIO 用来输出结果合并字符串
动态类型编译执行检查缺点与此同时程序变得更加简洁
对于将来扩展具有灵活性便是优点
Structural Subtyping
4 语言年轻 Go ,虽然一种静态语言吸取鸭子类型优点。Go
所谓继承关系类型可以具有其他类型之间代换也就是说类型
变量是否可以赋予一种类型数据 类型是否拥有共同方法决定例如
对于“A 变量只要数据拥有 A 提供所有方法那么这个数据可以赋值变量
这样类型结构确定代换类型关系称为结构类型(Structural Subtyping );
另一方面 Java 这样根据声明拥有继承关系类型具有代换类型关系称为名义
类型(Nominal Subtyping )。
结构类型类型声明必要由于并不需要根据事先声明确定类型之间
关系因此可以实现鸭子类型风格编程完全动态类型语言相比虽然增加
描述可以同时获得鸭子类型带来灵活性以及静态编译带来类型检查
优点可以相当划算交换
小结
这里我们 Ruby 、JavaScript 、Java 、Go 4 语言服务器客户端以及静态
动态角度进行对比 4 语言由于不同设计方针产生不同设计风格
大家是否有了些许了解
不仅仅语言其实设计权衡结果需求环境以及范式生出
设计学习现有语言设计及其权衡过程可以未来语言打下基础
108
----------------------- Page 120-----------------------
3.2 Go
2009
11 ,Google 发布一种名为 Go 语言世界范围引发轰动下面
编程语言设计者角度展望一下
(1) New (

Go
这个新兴编程语言。 (2) Experimental (实验
(3 ) Concurrent (
并发
作为一种编程语言,Go 宣扬 (4 ) Garbage-collected (垃圾回收 )
(5) Systems (
系统
1 中的这些关键字首先我们 (6) Language
这些关键字到底什么意思 1 Go 关键字
New (

几乎编程语言发布时候都会这样问题:“为什么创造
?”Ruby 发布当时这样只是因为而已这么
不着回答不过 Go 开发者这个语言由于现有语言不满诞生出来
10 编程语言相继诞生获得一定程度应用其中大多数是以
Ruby
代表动态语言但是触及 C C++ 领域系统编程语言迟迟没有出现
另一方面编程语言面临状况不断发生变化网络普及大规模集群
重视性能系统编程语言没有这样变化做出应对。Go 语言开发者主张正是因为
这样局面使得创造一种开发效率系统编程语言变得十分必要
Experimental (
实验
一种编程语言出现实用经历时间普通人想象 Ruby 为例
开始开发发布 3 左右时间到了程序员圈子拥有一定知名度 4
时间通过 Ruby on Rails 走红 5 时间
相比之下 2007 年末开始开发 Go ,经过 2 左右开发发布获得
全世界关注表示实在羡慕即便如此,Go 吸收那些概念是否真正
109
----------------------- Page 121-----------------------
3 编程语言潮流
接受现在还是未知数这个意义上来应该只是一种实验语言
Concurrent (
并发
21 世纪今天并发编程变得愈发重要需要同时处理大量并发访问网络应用程序
本来更加适合并发编程对于不断增大处理信息分布式并发编程
方案因而备受期待
此外限度利用甚至(Many-core )环境CPU 性能并发编程
显得尤为重要
因此为了实现开发效率并发编程编程语言本身必须具备支持并发编程功能
已经成为一种主流设计思路近年来 Erlang 这样并行计算中心编程语言到了
广泛关注正是由于上述背景引起
然而当前主流系统编程语言没有语言语言规格层面考虑到了并发编程
正是一点为了 Go 开发契机
Garbage-collected (
垃圾回收
需要对象自动进行回收从而实现对内空间循环利用这种垃圾回收(GC )
40 多年出现 Lisp 编程语言已经常识需要大量操作对象程序对于
对象是否继续使用完全把握判断然而如果对象管理出现问题
便导致十分严重 bug 。
如果忘记需要对象进行释放程序占用内存容量不断增大从而导致内存
泄漏(Memory leak )bug ;反过来如果释放仍然使用中的对象导致内存空间损坏
悬空指针(Dangling pointer )bug 。
bug 特点就是出问题地方实际引发问题地方往往距离
发现修复 认为具备一定面向对象功能编程语言,GC 不可或缺
机制
使 GC 走进普通编程领域得到广泛认知不得不 Java 带来巨大影响
Java
之前大家 GC 主流观点要么认为性能问题要么认为系统编程
需要 C++ 这样系统编程语言没有提供 GC 机制应该出于这个原因
110
----------------------- Page 122-----------------------
3.2 Go
然而现在情况作为 21 世纪系统编程语言,Go 具备 GC 机制从而减轻
管理消耗程序员负荷跟着减轻从而使得开发效率到了提高
Systems (
系统
刚刚我们不断提到系统编程语言这个说法那么系统编程语言到底怎样一类编程

至于严格定义其实不是十分清楚不过印象应该可以用来编写
系统性能十分重视语言定位上来应该 C C++ 覆盖领域
的确这个领域广泛使用语言便是最新 C++ (1983 不能算是
无法编译直接运行代码原本设计不会编译这样代码 Java ,
用作系统编程语言而且 Java 发布 1995 到现在已经 10 多年
进一步由于 Java 本身就是设计 JVM 运行因此即便通过 JIT 最新技术
高速觉得一种系统编程语言
Google 由于海量数据大规模集群处理较大需求因此便愈发需要一种
编程语言然而为了避免使用多种编程语言造成管理成本上升,Google 公司
项目能够使用语言进行严格限制只有 C、C++、Java 、JavaScript Python 5 ① 。
用于基础架构系统编程 C C++、兼具开发效率高性能 Java 、用于客户端编程
JavaScript ,加上开发效率动态语言 Python ,认为十分均衡选择
不过仔细看看的话用于系统编程 C C++ 显得有些古老对于最近获得广泛认知
语言面对开发效率支持机制 GC 显得不足。Go 出现领域带来
清新可以,Go Google 表达对于系统编程语言不满结果
Go
创造者
领导 Go 项目主要下面这些 ·派克(Rob Pike )、 (Ken Thompson )、 ·
不过非官方项目没有这样限制比如 Google 公司内部 Ruby 程序员貌似他们官方
项目使用 Java Python ,非官方项目使用 Ruby 。
111
----------------------- Page 123-----------------------
3 编程语言潮流
Robert Griesemer、Ian Lance Taylor、Russ Cox 。
其中 ·派克 ·超级名人

·
曾经创造 UNIX 参与B 语言 Plan 9 操作系统开发传说中的黑客

对我来说 ·派克布莱恩 ·柯林 合作名著《UNIX 编程环境留下
印象除此之外 ·派克 AT&T 贝尔实验室贡献诸多成果 Plan 9 开发
扮演重要角色要说我们最近功绩莫过于 UTF-8 开发 ·
共同成果如果没有他们的话估计现在世界已经那个 UTF-16 占领
这里不禁充满感激
虽然可能私情成分这样开发者创造 Go ,一定UNIX ,特别 C
影响甚至可以就是现代 C 语言因此下面我们通过 C 语言对比
介绍一下 Go。
Hello World
Hello World
可以介绍编程语言文章必需 2 这里世界
不是故意汉字而是 ·派克原始 Hello World 程序就是这么也许
证明 ·派克开发者之一缘故程序表明只要使用 UTF-8 字符串可以
驾驭 Unicode 。不过貌似标识还是只能英文数字
package main
程序属于名为“main”
import "fmt"
使用名为“ fmt”
func main () {
fmt.Printf("Hello,
世界 \n"); 使用 fmt中的Printf函数
}
2  Go 编写 Hello World
由于只能引用公有函数因此圆点后面跟着标识总是大写字母开头
印象,Go C 语言果然还是相似当然有一些不同例如 package
import
系统定义以及末尾分号可以省略等等
Printf
中的 P 大写字母一点引人注目其实背后代表规则写字
开头名称表示可以外部访问公有对象小写字母开头名称表示只能内部访
① Plan 9 ,
全称 Plan 9 from Bell Labs (贝尔实验室计划),贝尔实验室20 世纪 80 年代中期 2002 为止
研究 UNIX 后续可能性主要目的开发操作系统
布莱恩柯林(Brian Kernighan ,1942— )加拿大计算机科学家任职贝尔实验室 UNIX
开发者之一现在普林斯顿大学任教
112
----------------------- Page 124-----------------------
3.2 Go
私有对象由于有了规则,Go 几乎所有方法大写字母开头
Go
控制结构
下面我们更加深入了解一下 Go
首先我们控制结构开始。Go 主要控制结构 if、switch for 没有
while ,while
for 代替我们来讲一下 if 结构
Go
if 结构语法规则 3 。 if 条件1 {程序 1}
else if
条件2 {程序2}
其中 else if 部分可以任意。Go if 结构 C else {程序3}
if
结构相似不过几点区别 3 Go 控制结构
首先 C 语言不同,Go if 结构中的条件部分并不括号起来相应程序
部分必须花括号起来
有一点不是明显就是必须起来规则实际上非常重要。C 语言规则
这样程序包含代码需要花括号起来使成为一体这样规则,if
结构语法便产生歧义
例如
if (
条件) if (条件语句 else 语句
这样 C 语言程序到底解释
if (
条件) {
if (
条件 ) 语句
else
语句
}
还是解释
if (
条件) {
if (
条件 ) 语句
}
else
语句
貌似抉择
这个问题称为悬挂 else 问题”。 C 语言虽然存在歧义 else 属于距离
if 语句这样规则还是避免不了混乱发生
113
----------------------- Page 125-----------------------
3 编程语言潮流
然而如果有了程序必须花括号起来规则不会产生这样歧义
这个角度允许省略花括号着实主意其他广泛应用主流语言,Perl
语法允许省略花括号
,Ruby 控制结构划分没有使用花括号而是使用 end ,理由一样
Ruby 这样使用 end 语言可以避免悬挂导致歧义
Go
if 语句还有一点 C 语言不同
if v = f(); v < 10 {
就是条件部分可以允许使用初始化 fmt.Printf("%d < 10\n", v);
} else {
语句具体例如 4 。 fmt.Printf("%d >= 10", v);
}
初始化语句移动 if 结构 前面
4 条件部分可以使用初始化语句
意思不会发生变化初始化语句
条件部分 更加强调 f() 返回进行判断意图我们后面讲到逗号
OK”
形式这种初始化方式非常奏效
switch
C 语言
switch a {
些微差异 if 结构一样条件 case 0: fmt.Printf("0");
default: fmt.Printf("
0");
不用括号起来程序必须 }
花括号起来 5 )。
5 Go switch 结构
没有满足条件 case 执行 default 部分一点 C 语言相同但是 C
相比,Go switch 结构存在下面这些差异
‰
即便没有 break ,分支结束
‰ case
可以使用任意
‰
分支条件表达式可以省略
switch
中的 break 语法诡异堪称 C 语言这个机会正好上面
例子存在用于分隔 break 相应,case 可以并列多个条件
虽说 Go C 语言 继承东西没有必要这些一起继承过来然而,Go
追加语法 case 程序 fallthrough 语句结束进入下面分支
真的必要觉得只是一种 C 语言单纯怀念而已
C
语言 switch ,case 可以接受只能整数字符枚举编译已经确定
应该考虑实现分支进行优化结果 break 存在可以看成以前
语言编程派生
114
----------------------- Page 126-----------------------
3.2 Go
于是汇编语言已经十分罕见现在这种语法成为没有办法
Go 没有这样制约
最后,Go 还有一种有的有趣
switch {
语法就是 switch 语句 判断分支 case a < b: return -1;
case a == b: return 0;
条件表达式可以省略 6 )。
case a > b: return 1;
其实可以看成一种易读方式 }
实现 if-else 结构实际上编译出来 6 分支条件表达式可以省略
结果貌似一样
有趣,Ruby case 结构可以
case
同样形式 Ruby 编写出来 when a < b: return -1
when a == b: return 0
7 那个不是 Go when a > b: return 1
抄袭 Ruby 没有证据证明一点 end
觉得不大可能 Ruby ,不过 7 Ruby 可以省略分支条件表达式
实话小小地动一点这个念头
万一真的?(
for
结构 C 语言非常相似 if 结构一样条件表达式需要括号起来循环
必须花括号起来除此之外还有一些地方 C 语言有所不同
首先,Go for 语句 条件部分表达式形式表达式形式表达式形式
其中表达式形式 C 语言中的 while 语句功能相同
for
条件 {循环 }
表达式形式 C 语言 for 语句相同
for
初始化 ; 条件 ; 更新 {循环 }
因此编写出来循环代码 C 语言几乎一样
for i=0; i<10; i++ {
...
}
有趣 Go ,++ 递增不是表达式而是作为语句处理此外由于没有
C 语言逗号操作符形式因此 for 条件部分无法并列多个表达式如果多个变量
进行初始化可以使用多重赋值
115
----------------------- Page 127-----------------------
3 编程语言潮流
for i,j=0,1; i<10; i++ {
...
}
for 语句空白表达式表示因此
for ;; {
...
}
表示无限循环 C 语言一样
循环可以通过 break continue 中断 Loop: for i = 0; i < 10; i++ {
意思 C 语言一样不过 Go 可以通过 switch f (i) {
case 0, 1, 2: break Loop
指定到底跳出哪一个循环 8 )。 }
g (i)
一点有点 Java 风格看来 Go 设计 }
真是研究参考其他多种语言 8 标签指定跳出循环
作为控制结构还有一些 go 语句这样并发编程关系密切方式我们稍后
介绍
类型声明
正如我们刚才这些例子,Go 一种深受 C 语言不是 C++ )影响语言不过
Go
其他一些 C 派生语言不同地方 具有特色就是类型声明简单
Go
类型声明 C 语言正好相反
C
语言类型声明基本方式
类型 变量 ;
不过由于存在用户自定义类型因此遇到名称开头一行代码一眼
判断出来到底类型声明还是函数调用或者变量引用
人类存在歧义表达方式编译器意味着需要复杂处理才能区分
因此,Go 规定声明必须保留开头类型位于变量之后
根据规则,Go 声明 9 深受 C 语言影响这个印象真是令人
震惊不过习惯之后觉得那么特殊这样描述方式可以减少歧义无论还是
116
----------------------- Page 128-----------------------
3.2 Go
编译器更加友好。 // 类型声明
type T struct {
“:=”
赋值颇具魅力。“:=”赋值语句表示 x, y int
}
同时左侧变量声明右侧表达式类型
//
常量声明
采用静态类型语言由于需要大量类型 const N = 1024
描述因此程序通常显得比较冗长 Go // 变量声明
由于可以省略类型声明因此可以程序变得更加 var t1 T = new (T )
虽说如此这种类型推导并非某种函数 // 变量声明简略
一样完美因此 Go 并非完全需要类型声明。 t2 := new (T )
//
可以声明指针
Go
没有 C++ 模板(Template ),没有Java var t3 T = &t2*
(Generic ),内置数组(Array )、 // 函数声明
(Slice )、字典(Map )、通道(Channel )类型, func f (i int) float {
可以指定其他类型切片 Go 有的一种类型, ...
}
粗略可以理解数组指针字典类似
//
函数声明多个返回
Ruby
中的 Hash ;通道用于并发编程因此稍后 func f2(i int) (float, int) {
介绍。 ...
}
//
字符串数组 9 Go 声明
var a [5]string
//
字符串切片
var s []string
// int
字符串字典
var m map [int]string
// int
管道
var c chan int
目前由于Go 没有支持因此无法定义类型安全用户自定义集合
取出用户自定义集合元素需要使用 Cast。
Cast
语法如下
f := stack.get. (float)
Cast
执行进行类型检查不禁想起支持之前早期 Java
Go FAQ 没有否定增加可能性只不过优先级比较此外随着
支持类型系统变得非常复杂这些因素考虑的话暂时没有支持计划
117
----------------------- Page 129-----------------------
3 编程语言潮流
不过个人推测既然早晚支持那么现在是不是应该现有复合类型数组切片
字典声明具有统一性
继承面向对象
了解 Go 语言之后个人观点感触莫过于面向对象功能。Go
一种静态语言拥有起来感觉动态语言相近面向对象功能
其中特征就是继承不是基于原型(Prototype )那样实现方式。Go
面向对象机制其他语言大相径庭所以开始容易一头雾水
首先,Go 几乎所有对象对象
func (p Point) Move (x, y float ) {*
可以定义方法。Go 方法一种指定接收 ...
}
(Receiver )函数”,具体 10
10 Go 方法定义指定接收器
函数(Move )前面括号起来部分
“p *Point”
就是接收器接收器名称必须逐一指定一点麻烦不由得想到
Python 。
有趣方法定义类型定义可以完全不同地方进行有点 C# 中的扩展
可以现有类型添加方法
貌似 int 这样内置类型不能直接添加方法不过我们可以个别名叫 init ,然后
这个类型添加方法
方法调用方式还是比较普通
p.Move (100.0, 100.0)
-
C 语言不同语言本身可以区分是否指针因此需要自己判断“.”还是“ >”。
由于 Go 没有继承因此通常变量没有多态方法调用连接静态一种更加
说法也就是说如果变量 p Point 的话 p.Move 必定表示调用 Point 中的 Move 方法
然而如果只有静态连接的话作为面向对象编程语言缺少重要功能
Go 通过使用接口(Interface ),实现动态连接
Go
接口 Java 接口相似具备实现方法集合具体定义如下
118
----------------------- Page 130-----------------------
3.2 Go
type Writer interface {
Write (p []byte) int
}
interface
正文出现可能方法类型声明因此需要保留 func 接收器类型。“
需要东西正是 Go 风格
作为实现类型),接口定义方法类型它本身类型因此可以
用于变量参数声明于是只有通过接口调用方法进行动态连接
虽然语法有些差异大体上 Java 接口还是非常相似由于没有继承因此只能
接口实现动态连接这样便增加静态链接几率提升运行效率一点有意思
不过没有什么好处
Go
接口令人感到惊讶一点就是类型对于是否满足接口需要事先进行
声明
Java 如果定义 implements 子句接口进行声明表示一定
这个接口然而 Go 无论任何类型只要接口定义方法拥有类型
满足接口
以上 Writer 为例只要对象拥有接受 byte 切片 Write 方法可以进行代入
通过这个变量调用方法的话根据对象选择合适方法进行调用
这不就是动态语言推崇鸭子类型明明一种静态语言如此轻易实现鸭子
类型人情何以
例如我们前面经常提到 fmt.Printf 方法参数应该具有 String() 方法
反过来说只要 String() 方法进行重新定义可以控制 fmt.Printf 方法输出其实
上学时候曾经静态类型面向对象语言十分着迷曾经模糊设想类似
机制当时没有能力实现出来
Go
提供面向对象功能十分简洁兼具类型检查鸭子类型虽然当时没有
专有名词两者优点何等优秀设计非常感动
那么动态连接通过接口形式实现然而接口无法实现继承具备另一
功能实现共享”。 Java 即便使用接口无法共享实现因此大家普遍使用结合
(composite )技术
119
----------------------- Page 131-----------------------
3 编程语言潮流
对于一点,Go 考虑到了 Go 如果结构成员指定匿名类型
类型嵌入结构这里重要一点嵌入类型拥有成员方法
包含结构事实上相当于是多重继承这样一来大家可能成员方法
名称会不会发生重复? Go 通过下列这些独特规则解决问题
‰
重复名称位于不同层级外层优先
‰
位于相同层级名称重复并不引发错误
‰
只有拥有重复名称成员访问出错
‰
访问名称重复成员需要指定嵌入类型名称
最后规则好像不是容易看懂我们示例 11 )。
11 结构 C 嵌入其中
type A struct {
B 拥有 z 名称重复成员然而, x, y int
}
由于 z 位于外层因此优先如果访问
type B struct {
B 定义 z ,需要使用 B.z 这样名称。 y, z int
}
type C struct {
结构 A 结构 B 拥有 y 名称 A // x, y
重复成员因此包含 A B 嵌入 B // y, z --yA重复
z int // z
B重复
结构 C 重复 y 成员位于同一 }
层级 11 重复优先级示例
于是引用结构 C y 成员出错这种情况需要指定结构名称
A.y 、B.y ,这样访问成员不会出错这种设计真是相当巧妙
多值多重赋值
,Go 函数方法可以返回多个多值
返回需要使用 return 语句如果
//
函数定义多个返回
声明返回指定变量可以自动 func f3 (i int) (r float, i int) {
r = 10.0;
遇到 return 语句返回指定变量当前, i = i;
不必 return 语句指定返回 12 )。 return; // 返回 10.0 i
}
接受返回采用多重赋值方法 12 return 返回 r i
a, b := f3 (4 ); // a=10.0; b=4
120
----------------------- Page 132-----------------------
3.2 Go
Ruby
可以通过返回数组方式实现多值返回类似功能返回数组说到底依然只是
返回而已 Go 真正返回多个一点更加彻底
Go
错误处理使用多值机制 之下,C 语言由于只能返回具备异常
机制因此发生错误需要返回特殊 NULL 或者负值错误信息传达
调用
UNIX
系统调用(system call )调用(library call )大体上采用类似规则然而
这样规则正常可能错误发生重复因此总有碰钉子时候
例如,UNIX mktime 函数正常返回 1970 1 1 00:00:00UTC 开始指定
时间经过出错返回 -1。然而最近系统平台开始支持负值
-1 变成正常代表 1969 12 31 23:59:59 。
Go
没有异常处理机制通过多值可以原本返回基础同时返回错误信息
称为逗号 OK”形式
Go 打开文件程序 13 f,ok := os.Open (文件,os.O_RDONLY,0);
。 if ok != nil {
... open
失败处理 ...
}
这样程序错误正常可能
13 文件打开
混淆
一样异常处理搁置功能理由语言变得过于复杂不过有了
OK”形式一定程度可以弥补缺少异常处理不足
然而没有异常处理不方便地方就是 Java finally ,或者 Ruby ensure 部分
无论正常结束还是发生异常保证执行处理程序
Go 通过 defer 语句实现处理。defer 语句指定方法函数执行完毕
一定调用
例如为了保证打开文件最终关闭
f,ok := os.Open (
文件,O_RDONLY,0);
14 这样使用 defer 语句实现。 defer f.Close ();
14 使用 defer 关闭文件
Ruby ,open
close
Go 抽象虽然不如 Ruby 那样提供可以避免文件忘记关闭需要
基本框架
121
----------------------- Page 133-----------------------
3 编程语言潮流
并发编程
如果 Go 作为最新系统编程语言重要特征恐怕大多数都会——
编程
近年来虽然 Erlang 这些并发编程卖点编程语言到了广泛关注系统
领域没有出现这样语言系统编程领域实现并发编程只能 C、C++ 线程(thread )
艰苦卓绝斗争
然而线程东西并发编程绝对好用工具
Go
语言内置并发编程支持功能参考 CSP (Communicating Sequential
模型
Processes ,
通信顺序进程
具体方法使用 go 语句。go Go 有的语句也许 Go 这个命名来源
通过这个语句可以创建控制流程
go f (42 );
f
函数独立控制流程执行 go 语句后面程序并行运作
这里独立控制流程称为 goroutine ,控制流程还有其他一些表现方式 1
它们差异进行比较
1 “控制流程实现方法一览
表现控制流程术语 内存空间共享    上下文切换
process (OS ) no no
自动
process (Erlang ) no yes
自动
thread yes no
自动
fiber/coroutine yes yes
手动
goroutine yes yes
自动
其中内存空间共享控制流程是否可以访问其他控制流程内存状态
如果共享的话可以避免数据访问冲突并发编程有的难题另一方面
共享信息需要数据进行复制这样存在性能下降风险
① CSP (
通信顺序进程一种用来描述并行系统交互模式形式语言托尼霍尔(C.A.R.Hoare, 1934— )
1979 论文提出。CSP 并发编程语言设计深远影响影响编程语言包括 Limbo
Go

122
----------------------- Page 134-----------------------
3.2 Go
轻型程序是否可以创建大量控制流程例如操作系统提供进程(process )
线程(thread ),程序创建进程线程现实然而如果轻型控制
的话不要说上某些情况就是创建百万毫无问题
最后,“上下文切换程序是否需要控制流程进行切换例如fiber (
coroutine )
需要进行切换
除此之外可以等待输入暂停运行时或者一定时间间隔方式自动进行切换此外
支持自动上下文切换方式大多数情况支持 CPU 中的多个核心同时运行
Go
goroutine 支持 内存空间共享轻型支持自动上下文切换因此可以充分
性能实现根据核心数量自动生成操作系统线程 goroutine 运行
进行适当分配
此外通过使用自动增长 segmented stack 技术可以避免空间浪费即便生成
goroutine 不会操作系统带来负荷
在内空间共享并发编程如果同时同样数据进行改写发生冲突
导致数据损坏甚至程序崩溃因此必须引起充分注意 Go 为了降低发生
问题几率采取下面策略
第一策略作为 goroutine 启动函数推荐使用指针参数而是推荐使用
参数这样一来可以避免共享数据产生访问冲突
第二策略利用通道(channel )作为goroutine 之间通信手段通过使用通道
不必考虑互斥问题 通道通信方式并发编程有效性已经通过 Erlang
到了证实
通道一种类似队列机制
//
创建通道
侧写 另一读出 c := make (chan int);
c <- 42 //
通道添加
读出操作使用 <- 操作符完成 v := <- c // 取出变量
15 )。 15 <- 操作符使用示例
16 goroutine 通道编写简单示例程序这个程序通过通道多个
goroutine
连接起来这些 goroutine 分别 1,传递下一个 goroutine 。
开始通道 0 ,返回 goroutine 生成 goroutine 个数这个程序我们
成了 10 goroutine 。
123
----------------------- Page 135-----------------------
3 编程语言潮流
配备 Core2 Duo T7500 2.20GHz CPU 电脑运行这个程序需要不到 1
完成生成 10 控制这么时间还是相当不错
package main
import "fmt"
const ngoroutine = 100000
func f (left, right chan int) { left <- 1 + <-right }
func main () {
leftmost := make (chan int);
var left, right chan int = nil, leftmost;
for i := 0; i < ngoroutine; i++ {
left, right = right, make (chan int);
go f (left, right);
}
right <- 0; // bang!
x := <-leftmost; // wait for completion
fmt.Println (x ); // 100000
}
16 Go 编写并行计算示例程序
小结
Go
一种比较简洁语言我们这里依然无法网罗全部方面因此这里
Go
介绍某种语言设计者看待一种编程语言时候哪些吸引这个角度
出发
认为 Go 一种考虑十分周全语言多年 C 程序员还有 C++ 程序
历史)。作为一种系统编程语言稍许产生也许可以这样念头
学时 C 语言以来到现在还是一次
当然,Go 不是一种十全十美语言诸多不足如数字典特殊对待部分
以及作为一种静态语言总归还是需要做出一定支持
此外异常处理必要即便有可能语言变得复杂认为最好还是应该
方法重载操作符重载支持对于是否可以省略分号这样规则对我来说没有
直观感受
实现方面,Go 目标做到压倒性高速编译以及运行所需时间控制同等 C
程序 10% 20% 范围目前不要编译时间貌似运行时间尚未
当初目标
124
----------------------- Page 136-----------------------
3.2 Go
不过回过头来,Go 只是一种非常年轻语言 2007 项目启动只是
仅仅几年时间而已
一种编程语言出现广泛关注使用大多需要 10 以上时间 Go
几年时间着实令人惊叹
Go
下一代编程语言看好今后继续关注发展
实在 Go 出现之前已经存在一种叫做“Go!”语言由于
Google
奉行作恶”(Don t be evil )信条因此网上认为Go 应该改名。’
话说语言名称撞车不是什么新鲜 Ruby 这个名字编程语言几个),
网上有人推荐 Go 语言改成 Golang 或者 Issue-9。前者来自 Go 官方网站域名(golang.

org ),
后者来自已经Go! 语言改名这个问题报告编号
个人改名撞车撞车这个选项如果非要的话
比较喜欢 Golang 无论如何 Google 今后做出怎样抉择十分关注
① Issue-9
来源于 Google Code Go 语言 Issue tracker 中的 9 问题问题提交 2009 11 10 Go
刚刚发布几天之后),标题“I have already used the name for *MY* programming language”(已经 * 自己
* 编程语言这个名字),发帖人称自己已经开发 10 语言这个名字有人指出其实发帖
开发语言“Go!”,感叹号),并且发表论文出版。2010 10 ,Google 正式作出回应
Go 计算机相关产物过去 11 月中语言命名类似没有引起歧义因此决定
关闭问题。(Issue-9 链接:http://code.google.com/p/go/issues/detail?id=9 )
125
----------------------- Page 137-----------------------
3 编程语言潮流
3.3 Dart
2011
10 丹麦奥胡斯召开 GOTO 大会 2011 ,Google 公司发布一种编程
语言 Dart 。
GOTO
大会每年奥胡斯召开这个活动曾经叫做 JAOO (Java and Object Oriented ,
Java
面向对象),欧洲算是首屈一指技术大会。《代码重构作者马丁 ·(Martin

Fowler )、
维基创始人 ·宁安(Ward Cunningham ) 、“编程达人大卫 ·托马斯(Dave

Thomas )、C++
创始人 ·(Bjarne Stronstrup ) 著名技术先驱曾经
作为演讲大会发表演讲
自己演讲经历其中一次 2001 那个时候 Ruby on Rails 没有诞生
可以主办眼光十分敏锐所有演讲称赞大会讲师阵容豪华料理好吃堪称
大会”。
其实,David Heinemeier Hansson ③作为学生工作人员参加 2001 大会传说
饭局聊天机会 Ruby 产生兴趣从而 PHP 到了 Ruby ,之后
美国 37signals 公司开发 Ruby on Rails 。
关于 JAOO 好像有点虽说个人这个大会留下印象
不过这个话题还是到此为止下面我们回到主题来讲 Dart 。
为什么推出 Dart ?
“Dart 语言入门这样题材不如还是留给别的杂志图书网站
我们介绍重点关注隐藏 Dart 背后 为什么”。当然,Google 公司没有官方公布
宁安(Ward Cunningham ,1949— )美国计算机程序员维基(Wiki )概念发明者
(Bjarne Stroustrup ,1950— )计算机科学家,C++ 创始人现任德克萨斯
A&M 大学工程学院计算机科学首席教授
③ David Heinemeier Hansson (1979— )
丹麦计算机程序员,Ruby on Rails 创始人 Ruby 社区中常
名为 DHH 。
126
----------------------- Page 138-----------------------
3.3 Dart
推出 Dart 意图只是声明以及语言设计规格推测不过即便是以这些有限
出发点到了意外收获
那么,Google 到底为什么开发发布一种编程语言 Ruby 这样一个人
开始开发语言仅仅拥有技术兴趣而已这样理由足够成立
Google 公司作为一家世界具有代表性企业自己公司名义发布一种编程语言
觉得其中一定另有
况且知道 Google 公司这样规定公司内部软件开发项目只能
使用 C/C++、Java 、Python JavaScript 语言之所以规定因为使用语言
种类需要雇佣精通这些语言技术人员限制开发语言种类主要降低
管理成本上来考虑软件开发 Google 公司生命线技术人员兴趣角度上来
维系生命线需要管理大量代码这个角度毋庸置疑公司经营层面
非常合理判断因此虽然编程语言开发技术非常吸引 Google 不会
自己公司开发一种编程语言发布出来。Google 不惜违背自己规定开发一种自己
编程语言背后到底怎样原因
2009
Go 时候官方对于动机解释为了克服 C/C++ 缺点也就是说
Google
公司开发软件数量实在庞大 C 语言这样设计古老语言诞生 1972
便到了瓶颈 C++ 设计由于考虑 C 语言之间兼容性因此显得有些力不从心
因此,Google 公司开发人员希望能够提供一种
‰
现代
‰
安全
‰
十分高速
替代语言的确,Go 语言设计保持 C 语言同等程度高速同时加入简洁
面向对象功能垃圾回收机制以及类型安全特性
进一步推测的话 Google 这样需要编写大量代码公司即便没有什么外部用户
内部应该可以保证足够使用者虽然 Go 时间没有引起关注
Google 公司应该什么问题
,Dart
JavaScript 。JavaScript
美国 Netscape Communications 公司作为浏览器产品内部语言
开发开发周期非常
127
----------------------- Page 139-----------------------
3 编程语言潮流

JavaScript
设计者 ·(Brendan Eich ) 一次采访,JavaScript “几天
设计出来”。这样出身出乎意料算是一种不错语言由于开发
确实存在诸多不足例如开发周期导致语言规格实现过于追求简单化
程序员实际开发 JavaScript 代码容易变得十分繁杂
Dart 发布前夕曾经 Google 公司内部露出备忘录内容如下② :
‰ JavaScript
包含一些语言本质上缺陷这些缺陷无法通过语言改进解决因此
我们 JavaScript 未来执行方面战略
‰ Harmony (
风险回报): ECMAScript 标准规范小组 TC39 合作继续努力
JavaScript
进行改进
‰ Dash ③ (
高风险回报):保持JavaScript 动态性质同时开发一种容易提升性能
容易开发适合大规模项目所需工具语言——Dash 。

JavaScript ,
那么 Web 发展发生停滞从而可能苹果公司 iOS 对手竞争
反过来说如果放弃 JavaScript 专注 Dart ,一旦 Dart 失败 JavaScript 发展
停滞情况甚至危及 Google 公司技术地位因此,Google 做出这种
两手决定
对于备忘录,JavaScript 阵营尤其是语言创始人 ·回应性能
支持不是什么问题便是现在 JavaScript 有一些缺陷可以进行改善而且
JavaScript
下一个版本 Harmony 已经这些问题进行一定程度应对
此外,JavaScript 目前受到主要批判
‰
无法应对复杂互联网应用程序
‰
无法进行高速
‰
支持 /GPU
‰
无法修正
这里存在一些误解因此他们主张今后还是应该专注 JavaScript 。而且开发
语言可能造成社区分裂
(Brendan Eich ,1961— )计算机程序员,JavaScript 语言创始人现任 Mozilla 公司
技术(CTO )。
② Google
公司对于备忘录真伪没有做出官方表态。(
备忘录,Dart 称为 Dash ,应该项目途中更改名称。(
128
----------------------- Page 140-----------------------
3.3 Dart
技术正确与否只能留给将来历史证明我们这里判断双方孰优孰劣
认为他们双方主张具备各自合理性
下面我们具体看一看 Dart 这个语言
Dart
设计目标
Dart 主页 dartlang.org ① 关于 Dart 设计目标这样说明
‰
创造一种结构十分灵活 Web 开发语言
‰
Dart 程序员更加自然友好作为结果 Dart 设计一种容易学习语言
‰
构成 Dart 全部语言机制应该高速运行快速启动产生妨碍
‰
Dart 设计一种能够适应一切 Web 相关设备语言包括手机平板电脑笔记
电脑服务器
‰
主流现代浏览器提供可以高速运行 Dart 工具
需要实现上述目标 Web 开发者遇到问题下面这些
‰
小型脚本通常没有实现结构情况为了大型 Web 应用程序这样
程序进行调试维护而且这种应用程序无法多个团队分担开发
工作。Web 应用一旦变得巨大无法保证开发效率
‰
能够快速编写代码量化特性脚本语言受欢迎原因这样语言一般来说
对应程序模块访问约定契约),不是语言本身完成而是通过注释
结果除了作者以外读懂代码进行维护变得非常困难
‰
现存语言要求开发者必须静态类型动态类型两者选择一种传统静态
类型语言需要庞大开发工具编码风格制约比较感觉缺少灵活性
‰
开发者无法服务器客户端上构建具备统一系统。node.js Google Web
Toolkit (GWT )
为数例外
‰
多种语言格式混合导致繁杂上下文切换问题增加开发复杂性
原来如此作为动态语言信奉无法完全同意这些观点不过就算
应该理解那么既然意识这些问题存在为了实现设定目标,Google
Dart 设计成了怎样一种语言
语言名称 +lang”作为官方网站域名应该 Ruby 创建“ruby-lang.org”。虽然没有证据证明
Google
影响想象一下想法居然能够影响 Google ,感觉到一种莫名开心。(
129
----------------------- Page 141-----------------------
3 编程语言潮流
代码示例
首先我们 dartlang.org 上面
interface Shape {
示例程序 1 )。这个怎么说呢 num perimeter ();
}
Java
class Rectangle implements Shape {
不过仔细看看发现还是 final num height, width;
Rectangle (num this.height, num this.
例如数值类型叫做 num ,还有构造 width );
编写十分简洁方法定义其他形式。 num perimeter () => 2 height + 2 width;* *
}
此外,super 用法 Java 有些区别
指定方法这样形式有点 Ruby 。 class Square extends Rectangle {
Square (num size) : super (size, size);
}
我们另外示例程序 2 )。
1 Dart 示例程序(1)
好像风格变得有点不一样
怎么样是不是感觉 Java 有点相似
main () {
Java 简洁说起 Java 语法相似 var name = 'World';
① print ('Hello, ${name}!');
脚本语言想起 Groovy 。Dart 作为 }
JavaScript
试图简洁编程提供
2 Dart 示例程序(2)
支持大家是否从中感觉到了“$”
表达式嵌入字符串一点倒是
class Greeter {
语言风格。Ruby 差不多只不过 var prefix = 'Hello,';
“#”不是“$”。对了,Groovy “$”
greet (name) {
字符串嵌入表达式。 print ('$prefix $name');
}
Dart
无需指定类型程序 main }
方法作为起点。Dart 特征在于 main () {
声明可以省略关于这种强制 var greeter = new Greeter ();
greeter.greet ("Class!");
静态类型机制我们稍后详细进行探讨。 }
下面我们创建 3 )。 3 Dart 示例程序(3)
Groovy 风格这个程序简单就是创建调用方法好像没有什么
必要
① Groovy
一种 Java 平台面向对象编程语言发布 2003 一种动态语言可以作为 Java 平台
脚本语言使用
130
----------------------- Page 142-----------------------
3.3 Dart
Dart
可以创建多个构造方法那么我们定义
class Greeter {
指定问候构造方法 4 )。深受Ruby 毒害 var prefix = 'Hello,';
肯定这种功能方法实现好了。 Greeter ();
Greeter.withPrefix (this.
Dart
实例变量默认公有(public )可以 prefix);
greet (name) {
进行访问因此: print ('$prefix $name');
}
greet.prefix = "Goodbye" }
可以改写实例变量如果希望公开实例变量 main () {
var greeter = new Greeter
的话需要实例变量声明私有(private )。此外, .withPrefix ('Howdy,');
为了属性访问进行抽象可以定义 setter getter greeter.greet ("Class!");
}
方法如果 3 程序修改一下 prefix 私有化
4 Dart 示例程序(4)
setter getter 进行封装变成 5 样子
class Greeter {
String _prefix = 'Hello,'; // Hidden instance variable.
String get prefix() => _prefix; // Getter for prefix.
void set prefix (String value) { // Setter for prefix.
if (value == null) value = "";
if (value.length > 20) throw 'Prefix too long!';
_prefix = value;
}
greet (name) {
print ('$prefix $name');
}
}
main() {
var greeter = new Greeter();
greeter.prefix = 'Howdy,'; // Set prefix.
greeter.greet ('setter!');
}
5 Dart 示例程序(5)
首先名字“_ ”开头变量有的有的实例变量无法外部进行访问。setter
getter
方法前面分别加上 set get 。 Dart ,setter/getter 一般方法明确区分
因此无法定义 setter/getter 重名方法此外无法一类方法
最后我们简单说明一下拥有静态类型语言必然需要参数类型因此
Dart
理所当然具有
静态类型语言通过是否拥有参数类型看出语言设计时候对于类型
131
----------------------- Page 143-----------------------
3 编程语言潮流
程度考量想起早期 Java C++ 没有模板话说回来
那些语言出现时间一定程度也许没办法
这里 Java 一下
class Greeter {
Java
其实早期探讨引入 var name;
Greeter (this.name);
类型考虑当时参数类型
greet() {
处于研究水平恐怕设计 print('Hello ${name}.');
}
规格达成一致因此早期 }
规格放弃这个功能
main() {
List<Greeter> greeters = [new Greeter ("you"),
那么作为现代静态类型 new Greeter("me") ];
,Dart 采用我们在前 for (var g in greeters) g.greet();
}
面的示例尝试一下
6 Dart 示例程序 (6 )
虽然有些牵强 6 我们使
List<Greeter> 存放多个 Greeter 当然由于 Dart 类型声明是非强制因此
var 程序一样可以正常运行
Dart
特征
刚才我们 Dart 进行快速了解。Dart (目前不是一种规格规模语言
篇幅不可能涵盖全部特性不过至少大家基本风格有所了解
因此,Dart 特征尤其是 JavaScript 进行比较的话认为比较重要应该
‰
基于对象系统
‰
强制静态类型
当然除此之外还有其他一些细微差异如果说 Dart JavaScript 之间决定性差异的话
上述莫属
基于对象系统
JavaScript 对象实现基本上列表(hash table )方式。JavaScript 除了
数值字符串几乎所有数据对象列表或者函数函数对象),也就是说基本上
数据结构以不变应万变”。
132
----------------------- Page 144-----------------------
3.3 Dart
列表数据取出访问数量级 O(1) ① ,无论大小如何一定速度取出数据
一种优秀数据结构遗憾直接访问数组结构相比无论数据取出
更新所需时间长得
Google Chrome 内置 v8 代表现代 JavaScript 引擎作为优化一部分
一定条件情况对象结构方式实现然而,Dart 天生具备基于对象
系统因此需要进行这种不自然优化行为作为结果简单引擎实现高性能
非常值得期待
话说,JavaScript 下一个版本 Harmony 采用基于对象系统虽说这样一来
JavaScript
方面面临原有版本兼容性问题可以想象今后 Dart 优势逐渐削弱
强制静态类型
Dart
特征莫过于强制静态类型由于类型描述程序本身逻辑没有直接
关系因此觉得类型十分繁琐类型并非一无是处首先虽然类型信息
程序逻辑没有直接关系属于重要附属信息通过类型矛盾经常可以检查程序错误
虽说程序中的类型信息没有矛盾并不代表程序没有错误至少相当错误
通过类型信息机器检查出来
此外通过程序附加类型信息使得编译可以用来进行优化信息增加
可能生成品质高性能代码进一步,IDE 工具自动完成辅助功能可以
利用类型信息
静态类型如此好处另一方面规模程序如果强制类型信息进行描述的话
类型信息比例相当从而使得程序逻辑本质埋没消磨开发欲望
为了解决这个矛盾某些语言采用类型推导(type inference )机制Dart 采用
强制省略静态类型”(optional typing )方法Dart 没有指定类型变量
表达式当做 Dynamic 类型检查运行时完成
采用强制系统语言并非只有 Dart ,这些语言问题在于如果类型信息是非
省略运行过程类型信息逐渐减少导致进行类型检查范围不断缩小
结果编译可以发现错误静态类型具备优势没了一半此外随着类型信息
减少能够用于优化信息同时减少一点上来有点得不偿失
算法,O N( ) (O 记法表示情况一种算法性能与其对象数据之间关系。O(1) 表示
性能数据无关数据大小影响算法性能关于 O 记法解释参见 4.1 中的相关内容
133
----------------------- Page 145-----------------------
3 编程语言潮流
基于这些问题,Dart 进行大胆突破也就是说,Dart 类似 JavaScript 这样语言
本质层面具备类型信息动态语言静态类型信息仅仅作为辅助地位存在 Dart
语言规格明确记载 Dart 具备完全进行类型检查工作模式也就是说没有
打开类型检查情况例如
num n = "abc";
这样程序完全可以正常运行
大概这样到底什么好处说实话同样疑问
大胆推测一下如果使用类型信息,IDE 自动完成功能已经十分有效
而且程序进行一定程度类型检查其一另外随着自己开发程序规模逐渐
扩大可以阶段性增加静态类型信息从而同时享受动态类型静态类型双方优点
这样一说的话好像与此同时还是这种机制是否能够成功表示怀疑
Dart
未来
那么这样背景诞生 Dart ,今后会不会普及
个人认为,Dart 未来不能多么光明理由首先就是期望现实
差距
一种编程语言并不有了语言引擎就算完成而是必须这种语言得以立足框架
应用程序生态圈成熟起来之后价值真正开始体现需要
多年时间。Dart 诞生 Google 公司这样名门天生赋予期望实际
建立自己生态圈成为一种语言花费时间并不其他语言什么不同
Dart
是否能够忍受期望现实之间差距目前还是未知数
此外,Dart 当初目标为了打倒 JavaScript ,对手拥有大量用户社区应用程序
作为新手 Dart (尽管Google 公司作为后盾仿佛赤手空拳一般基于对象系统也好
强制静态类型也好虽然不错概念这些是否具备足够独创性魅力弥补
前面压倒性劣势只能表示怀疑还有 Dart 实用之前,JavaScript 一定
进一步进化战斗形势十分严峻
话说 回来编程语言一种“10 幼儿园小孩水平耐久领域未来
预测我们只能继续关注 Dart 发展
134
----------------------- Page 146-----------------------
3.4 CoffeeScript
最近,JavaScript 发展十分惊人一种语言试图 JavaScript 威风争得一席之地
我们介绍一下这种语言——CoffeeScript。
普及语言
世界编程语言种类相当据说成千上万要说其中普及或者说法
引擎安装数量最多语言恐怕 JavaScript 莫属
最近 计算机用户大会编程几乎所有都会访问网站访问网站甚至
成为上网代名词现在世上几乎所有 Web 浏览器内置 JavaScript 引擎。PC
Internet Explorer 、Firefox 、Safari、Chrome
自不必说智能手机甚至是非智能手机浏览
上都 JavaScript 引擎
随着移动设备兴起尤其是考虑智能手机普及完全可以断言 JavaScript 就是
普及语言正是因为有了如此普及进一步推动重要性不断上升
误解最多语言
另一方面,JavaScript 可以误解最多语言
JavaScript
Netscape Communications · 1995
用于扩展浏览器功能编程语言 名为 LiveScript ,当时正好美国 Sun
Microsystems
公司 Oracle 公司收购 Java 方兴未艾之际加上 Netscape Sun 之间
业务合作因此为了市场宣传冲击力名为 JavaScript 。JavaScript
使用花括号看上去 Java 有点语言核心意义部分 Java 完全不同因此
这个名字便成了招致重大误解元凶
JavaScript 成名时候经常听到类似“JavaScript 就是 Java 这样说法
认为只要学会 Java 学会 JavaScript 。
135
----------------------- Page 147-----------------------
3 编程语言潮流
JavaScript
本来目的为了编写点击网页按钮所需一些简单处理逻辑一点
招致第二误解——JavaScript 只能完成简单工作简易语言然而实际上出乎意料
JavaScript
一种设计良好语言拥有基于原型面向对象功能可以函数作为对象使
在此基础提供正式闭包功能由于可以进行函数编程因此语言语义
接近 Scheme 一面
JavaScript 良好设计微软公司实现动态网页 DynamicHTML , Google 地图
这样大量运用 JavaScript 网站开始不断出现人们对于 JavaScript 印象发生转变
Google
地图 Ajax(Asynchronous JavaScript and XML ,异步JavaScript XML )编程风格先驱
如今使用 JavaScript 制作视觉特效以及 Ajax 实现页面迁移网站已经一点稀奇
当初,JavaScript 作为 Netscape Navigator 浏览器内置客户端语言问世后来逐渐内置
其他一些浏览器然而由于公司 JavaScript 实现参考 Netscape 基础独自
开发因此浏览器之间兼容性程序员感到十分痛苦早期 JavaScript 程序员
需要运用各种各样方法判断浏览器类型 回避兼容性问题做出努力 1997
ECMA ①规范作为“ECMAScript”实现标准化以来问题到了改善即便
如此依然还有一些使用版本 Internet Explorer ,因此大家没有完全兼容性问题
解放出来
最后误解关于性能。JavaScript 变量表达式没有类型信息具备动态性质因此
性能 Java 静态类型语言相比具有劣势实际上早期 JavaScript 引擎实现没有
追求性能然而随着 JavaScript 应用范围扩大一点到了巨大改善
显著高速语言
作为编程语言经常关注一点就是同样算法各种不同语言实现时候
相互之间多少性能差异一点一般认为采用获得性能改善信息静态类型
编译器作为引擎语言性能比较例如 C++ Java
然而根本 应该引擎性质有关语言种类无关因此
① ECMA
”(European Computer Manufacturer
Assosications ),
后来随着组织国际化沿用 ECMA 这个名称一般称为 ECMA International ),
代表之前全称组织 JavaScript 基础制定标准化规范“ECMAScript”编号 ECMA-262 ,
JavaScript
规范兼容扩展版本
136
----------------------- Page 148-----------------------
3.4 CoffeeScript
JavaScript
动态语言因此速度这种印象并非普遍事实而是语言引擎实现做出
多大努力决定
的确早期 JavaScript 引擎性能并不然而随着 JavaScript 广泛使用重要性
跟着提高 JavaScript 引擎投资到了扩大各种高速引擎相继问世刚刚诞生之际
Java ,
由于需要通过字节解释器工作 C++ 原生语言相比速度不少甚至有人:“
东西完全不能。”仅仅不久,Java 性能到了大幅度改善现在某些情况
甚至能够实现超越 C++ 语言性能 JavaScript 现象十分类似
最近 JavaScript 引擎 由于采用 JIT 、特殊垃圾回收技术动态语言
已经可以归入速度级别
JIT
Just In Time Compiler 缩写程序运行时编译机器语言技术
编译机器语言程序可以 CPU 原本速度运行因此能够克服解释器带来劣势
JIT
JVM (Java Virtual Machine ,Java 虚拟机到了运用
所谓特殊 一种函数转换内部表达运用技术通过假定参数特定
类型事先准备特殊高速版本函数调用开头执行类型检查当前条件成立
直接运行高速版本动态语言运行速度理由之一就是因为运行时需要伴随大量
检查通过特殊可以回避不利因素
垃圾回收一种不再使用对象进行回收垃圾回收(Garbage collection )算法
垃圾回收有一些比较普通方法标记清除这种方法程序变量引用对象
递归扫描标记存活对象”,认为剩下对象将来不再访问作为死亡对象
进行回收这种方法缺点程序生成对象数量为了找到存活对象所需扫描
如果运行时间一部分消耗垃圾回收的话性能降低。JavaScript
程序随着规模扩大对象数量跟着增加采用标记清除带来性能下降问题
愈发显著
改善这个问题 方法就是回收大部分程序中都存在这样一种趋势
生成对象大半使用时间不再访问存活下来一部分对象
拥有非常寿命回收对象分为新生代老生根据情况可能划分
新生代频繁进行扫描老生偶尔进行扫描从而减少整体
扫描次数
由于上述这些技术运用,JavaScript 为数动态语言跻身速度行列
Ruby
当然超越感到相当寂寞
137
----------------------- Page 149-----------------------
3 编程语言潮流
JavaScript 不满
那么虽然 JavaScript 人气如此使用如此广泛随着用户数量增加还是招致
越来越不满
JavaScript
语法语义上来非常简单基本上一种非常优秀语言然而
有些过于简单程序容易变得冗长感到不满多年以来一直主张过于简单
语言一定不会程序员开心因此不满可以应验观点
为了语言功能变得更加丰富出现一些 prototype.js 、jQu ery 之类其中增加
一些方法 JavaScript 对象起来 Ruby 感觉当然这些提供功能并不限于
CoffeeScript
于是为了解决 JavaScript 语法不满,CoffeeScript 做出尝试。CoffeeScript
Jeremy Ashkenas
开发 。Ashkenas 拥有多种编程语言经验开发用于 Ruby 访问视觉
设计语言 Processing ① Ruby Processing 。
也许出于这样背景,CoffeeScript 语法貌似 Ruby Python 影响两者
的话应该还是 Python 影响大一些
所谓 CoffeeScript ,一言以蔽之就是 Javascript 实现 用于编写 JavaScript 方便语言
CoffeeScript
一种可以弥补 JavaScript 缺点不满拥有独自语法编程语言 JavaScript
之间完全没有兼容性 ,CoffeeScript 程序运行需要编译 JavaScript ,然后作为
JavaScript
程序运行也就是说虽然程序看上去完全不同语义部分却是完全相同
进一步,CoffeeScript 编译器 JavaScript 编写也就是说只要 JavaScript ,
CoffeeScript
编写程序可以浏览器直接运行多语言因为无法客户端使用从而
不得不转向服务器环境性质可以 CoffeeScript 巨大优势
基于这些优势以及我们后面介绍 CoffeeScript 具有其他优秀性质,Ruby on Rails
3.1 版本开始正式采纳 CoffeeScript。
① Processing
一种用于视觉设计绘图开源编程语言 IDE 开发环境),发布 2001
138
----------------------- Page 150-----------------------
3.4 CoffeeScript
安装方法
CoffeeScript
安装方法好几 Ubuntu Debian Linux 环境可以平常一样
作为软件包进行安装
$ sudo apt-get install coffeescript
表示换行
或者可以使用 node.js (参见6.4 ),通过软件包系统 npm 进行安装
$ sudo npm install coffee-script
除此之外情况可以 http://coffeescript.org/ 下载 tar.gz 文件
安装完毕之后可以使用 coffee 命令输入 coffee -h 可以显示命令行选项一览
基本用法
$ coffee
程序 .coffee
(CoffeeScript
程序一般 .coffee 作为扩展名可以直接运行文件保存 CoffeeScript 程序
CoffeeScript 程序编译 JavaScript ,可以使用“-c”选项
$ coffee -c
程序 .coffee
结果输出扩展名替换 .js 文件编译 JavaScript 程序
声明作用
自己几乎没有 JavaScript 编程经验不过身为 JavaScript 程序员好友
JavaScript
不满之一就是变量声明作用
JavaScript 局部变量需要保留“var”进行声明如果小心忘记声明
变量变成全局变量由于全局变量任何地方可以访问修改于是变成孕育
bug
可怕温床
CoffeeScript 一点进行反省对于变量引用规则做出一些修改首先
声明需要 var ,而是通过赋值进行函数第一赋值语句视为局部变量
一点 Ruby Python 十分相似例如
foo = 42
139
----------------------- Page 151-----------------------
3 编程语言潮流
CoffeeScript 只是单纯赋值语句编译 JavaScript 变成
var foo;
foo = 42;
CoffeeScript
减少声明看上去更加简洁
由于 CoffeeScript 通过赋值语句所有变量声明局部变量因此创建全局变量
不可能 JavaScript 不同,CoffeeScript 位于顶层变量不是全局变量而是局部变量
“-b”选项进行指定)。
此外由于存在局部变量声明因此外侧作用存在同名变量以外
变量优先如果无意中使用同名变量有可能产生难以发现 bug 。Ruby 同样
问题 Ruby 1.9 之后版本可以通过代码作用有的局部变量进行声明来回
问题
CoffeeScript
可以变量前面加上 @ 进行引用相当于
this.
变量
缩写实例变量引用使用“@”一点 Ruby
此外变量末尾可能出现“?”。这种写法好像 Ruby 实际上
完全不同。Ruby 如果方法末尾加上“?”,表示方法谓词方法返回真假
方法)。 CoffeeScript 变量后面加上“?”表示变量 null undefined 以外”。
因此这个概念进行类推
a ? b
表示 a null undefined b ,
a?()
表示 a null undefined undefined ,否则 a 作为函数进行调用
a?.b
表示 a null undefined undefined ,否则引用 a 中的 b 属性”。
例如“a?.b”编译 JavaScript 1 。undefined 检查方法非常简单容易理解
由于方法出错遇到常时返回 null undefined ,如果使用这个功能的话可以
出错跳过后面处理逻辑从而程序变得更加简洁
140
----------------------- Page 152-----------------------
3.4 CoffeeScript
typeof a === "undefined" || a == undefined ? undefined : a.b;
1 “a?.b”编译结果
CoffeeScript
支持多重赋值
[a, b] = [1, 2]
表示 a 赋值 1,b 赋值 2 。 Ruby 不同不仅数组字典(map )可以
进行展开多重赋值
{a, b} = {a: 3, b: 4}
表示 a 赋值 3 ,b 赋值 4 。此外可以指定变量
{a:foo, b:bar} = {a: 3, b: 4}
表示 foo 赋值 3 ,bar 赋值 4 。
多重赋值看似简单其实编译 JavaScript 之后变得相当复杂 2 )。
var _a, _b, a, b, bar, foo;
// [a,b] = [1,2]
_a = [1, 2];
a = _a[0];
b = _a[1];
//{a:foo, b:bar} = {a: 3, b: 4}
_b = {
a: 3,
b: 4
};
foo = _b.a;
bar = _b.b;
2 多重赋值编译结果
分号代码
个人认为,CoffeeScript 重要改善就是上面讲到声明省略以及全局变量
解决然而 CoffeeScript 编写程序之后留下印象并非上面
而是分号省略以及通过进来表现代码
CoffeeScript Python 一样通过进来表现代码例如匿名函数可以这样
(a) ->
141
----------------------- Page 153-----------------------
3 编程语言潮流
console.log(a)
a a*
不必每次 function 同时可以匿名函数非常简洁方式表达出来由于
JavaScript
函数作为对象对待因此可以使用高阶函数编程风格匿名函数表达
十分繁琐经常感到非常痛苦而且,CoffeeScript 最后求值表达式自动成为
返回必须 return JavaScript 相比程序表达更加简洁
值得注意包括代码在内作为参数情形同样进来表现代码
Python
创建匿名函数 lambda 表达式函数只能采用单一表达式函数
作为对象使用必须作为局部作用域进行命名定义这个规则显得相当麻烦
作为后起之秀,CoffeeScript 自然考虑到了这个问题只要括号整个起来可以当做
表达式使用例如下面这样
something(((a)->
console.log(1)
a a), 2)*
缩进表现代码不仅可以用于匿名函数 if while 结构同样有效例如
if cond()
1
else
2
这样块状结构程序只有一行可以一行进行表达
sq = (a) -> a a*
或者
a = if cond() then 1 else 2
正如上述例子,CoffeeScript 中的 if 语句实际上表达式可以返回因此
“~?~:~”
这样三项操作符没有必要使用作废
省略记法
正如缩进表现代码一样,CoffeeScript 设计方针表达尽量简洁例如,JavaScript
为了分隔语句必须使用分号 CoffeeScript 完全需要使用分号
函数调用包围参数括号只有参数可以省略不过参数没有
142
----------------------- Page 154-----------------------
3.4 CoffeeScript
时候无法区分到底调用函数还是函数对象进行引用因此这种情况调用函数
还是加上 () (3 )。
CoffeeScript
对象括号可以省略 4 )。
JavaScript
写法#
obj = {a:1, b:2}
省略括号#
参数括号可以省略# obj = a:1, b:2
console.log("hello")
换行进来表现#
console.log "hello"
逗号省略#
a = -> 1 obj =
a
返回 1函数对象# a:1
a()
调用函数返回 1# b:2
3 函数调用情况 4 对象括号可以省略
字符串
CoffeeScript
字符串讲究首先,Ruby 具备表达式嵌入功能如下
字符串中用“#||”包围起来表达式嵌入字符串
name = "Matz"
console.log "Hello {name}"#
此外可以 Python 一样三重引号表示字符串 5 )。
三重引号需要类似 XML 字符串
换行忽略ab#
程序中的情况非常有用有趣这里 console.log "a
b"
CoffeeScript
Ruby Python 平等借鉴它们 换行有效#
功能。 a#
b#
console.log """a
话说,CoffeeScript 注释 Ruby 、Python b
“#”开头(JavaScript “//”),三重 """
进行类推,“###”表示直到下一个“###”为止 5 三重引号表示字符串
内容全部注释
数组循环
CoffeeScript
中的数组讲究不过遗憾数组没办法对象一样省略外侧括号
143
----------------------- Page 155-----------------------
3 编程语言潮流
ary = [
1
2
]
数组省略记法比如看上去 Ruby 范围表达式
[1..4]
这种写法表示 1 4”,编译 JavaScript 结果如下
[1,2,3,4]
不过如果范围端的任一使用变量的话编译出来变成 6 这样复杂结果
大家 JavaScript 数组 不满就是针对内容循环比较 7 )。于是
CoffeeScript
,for~in~ 循环为数专用对于对象成员访问使用一种 for~of~ 循环
实现为了大家理解它们区别我们 8 中的 CoffeeScript 程序编译 JavaScript
显示 9
// (a)
本来获取数组内容
var ary, i;
ary = [7,8,9,0];
for (i in ary) {
console.log(i);
}
//
结果显示不是内容而是索引
// (b)
获取数组内容使用如下方法
var _i;
for (_i = 0, _len = a.length; _i < _len; _
# a=4; [1..a] i++) {
var a, _i, _results; i = a[_i];
a = 4; console.log(i);
(function() { }
_results = []; //
这样才能真正显示数组内容
for (var _i = 1;
1 <= a ? _i <= a : _i >= // (c) for in
原本面向对象~ ~
a; var obj;
1 <= a ? _i++ : _i--) { obj = {foo: 1, bar: 2};
_results.push(_i); for (i in obj) {
} console.log(i);
return _results; }
}).apply(this); //
可以取得对象成员名称
6 数组范围表达式编译结果 7 JavaScript 数组循环
144
----------------------- Page 156-----------------------
3.4 CoffeeScript
var ary, i, obj, _i, _j, _len,
_len2;
ary = [7, 8, 9, 0];
obj = {
ary = [7,8,9,0]; foo: 1,
obj = {foo: 1, bar: 2}; bar: 2
#
数组循环显示元素) };
for i in ary for (_i = 0, _len = ary.length;
console.log i _i < _len; _i++) {
i = ary[_i];
#
对象无法循环 console.log(i);
#
因为不是数组 }
for i in obj for (_j = 0, _len2 = obj.length;
console.log i _j < _len2; _j++) {
i = obj[_j];
~ ~ ~ console.log(i);
# for of
相当于JavaScriptfor in
#
显示成员名称 }
for i of obj for (i in obj) {
console.log i console.log(i);
}
#
显示索引 for (i in ary) {
for i of ary console.log(i);
console.log i }
8 CoffeeScript for 循环 9  8 程序编译结果

JavaScript
基于原型面向对象语言因此并不基于语言一样具备直接支持
方法定义功能语法另一方面,JavaScript 虽然提供用于原型生成对象 new
不知为何作为原型却是函数对象总是感觉
虽然可以一种策略不过作为
class Person
长期以来习惯基于面向对象语言 构造方法#
多少觉得痛苦 ,CoffeeScript Rubyinitialize,Python__init__#
constructor: (name) ->
class 语句可以做到看上去基于 @name = name
面向对象语言实际上版本 JavaScript # 继承
提供 class 语句出于兼容性考虑, class SalaryMan extends Person
constructor: (name, @salary) ->
CoffeeScript
使 JavaScript class 调用方法#
语句。 super(name)
earn: => console.log "you earn {@#
salary} YEN a month."
CoffeeScript
class 10 salaryman = new SalaryMan("Matz",
100)
CoffeeScript
简洁相比编译 JavaScript 之后
10 CoffeeScript 定义
145
----------------------- Page 157-----------------------
3 编程语言潮流
结果显得十分复杂当然 JavaScript 硬生生配合 CoffeeScript “面子工程结果
也许并不一种公平比较
10 还有一些有意思地方比如方法可以 Ruby 一样使用 super ,以及
方法参数加上“@”可以不必通过赋值实例变量进行初始化
此外 10 还有一点值得注意 SalaryMan earn 方法定义用于函数对象
不是“->”而是“=>”。 CoffeeScript ,“=>”称为箭头(fat arrow )。
JavaScript
目前通过 this 表达上下文说实话,this 哪一个时间点绑定什么
一点有些难以理解尤其是事件回调情况调用函数,this 到底指向哪里
实际试验一下的话想象出来箭头定义函数对象上下文固定局部上下文
作为方法进行定义,this 永远指向接收器这样一来关于 this 麻烦除了
还有一点 10 程序没有体现就是方法究竟应该如何定义我们可以利用
class
实体 this 绑定正在定义一点使用“@”记法即可
class Foo
@number = 0
@inc: => @number++
constructor: ->
Foo.inc()
console.log Foo.number
这里,@number 对象 Foo 实例变量,@inc 方法调用方法需要这样
Foo.inc()
进行调用需要注意对象实例变量创建复制并不共享
也就是说即使
class Bar extends Foo
Bar.inc()
Foo
实例变量不会发生变化
小结
这里我们无法涵盖 CoffeeScript 全部特性除了上面提到之外还有十分方便
功能。CoffeeScript 印象发挥 JavaScript 优势同时为了消除 JavaScript 不满
借鉴 Ruby Python 多种语言功能虽然原本过于简单 JavaScript 更加复杂一些
146
----------------------- Page 158-----------------------
3.4 CoffeeScript
感觉语言设计体现一种绝妙平衡抛开利害关系甚至觉得有些地方
Ruby 更加优秀
CoffeeScript
编译器通过 JavaScript 编写编译结果 JavaScript ,因此只要
JavaScript
引擎无论任何环境可以工作何况现在 JavaScript 引擎可以遍地
CoffeeScript
利用这个优势无论服务器还是客户端今后应用范围都会越来越广可以
将来值得期待语言之一
147
----------------------- Page 159-----------------------
3 编程语言潮流
3.5 Lua
Lua
巴西里约热内卢天主教大学 Roberto Ierusalimschy 开发一种编程语言
所知诞生南美洲全世界范围获得应用编程语言,Lua 应该第一当然
知道除了 Lua 之外有没有其他语言来自巴西
编程语言及其作者国籍多种多样 1 ),大家可以看出并不所有
语言诞生美国相反貌似还是欧洲阵营更加强大一些尤其是人口比例的话
北欧语言设计者比例相当来自南美只有 Lua ,来自亚洲只有 Ruby ,真是
寂寞
1 编程语言开发者国籍
        
Fortran John Bacus
美国
C Dennis Ritchie
美国
Pascal Niklaus Wirth
瑞士
Simula Kristen Nygaard
挪威
C++ Bjarne Stroustrup
丹麦
ML Robin Milner
英国
Java James Gosling
加拿大
Smalltalk Alan Kay
美国
Perl Larry Wall
美国
Python Guido van Rossum
荷兰
PHP Rasmus Lerdof
丹麦
Ruby
本行 日本
Eiffel Bertrand Meyer
法国
Erlang Joe Armstrong
瑞典
Lua Roberto Ierusalimschy
巴西
话说 Lua 这个葡萄牙语月亮意思。Lua 特征一种便于嵌入应用程序
中的通用脚本语言设计思想相似语言还有 Tcl (Tool Command Language )。Tcl 语言
规格控制小的规模数据类型只有字符串一种 Lua 具备所有作为通用脚本
语言功能
148
----------------------- Page 160-----------------------
3.5 Lua
实现上来,Lua 解释器完全 ANSI C 范围编写实现可移植性
另外,Lua 高速虚拟机实现非常有名虚拟机性能评分中都取得优异成绩
示例程序
首先 1 简单 Lua 示例程序这个程序
--
Lua“--”开始行为单行注释
可以计算阶乘。 --阶乘计算程序
function fact(n)
if n == 1 then
怎么样? end 用法等等是不是有点 Ruby 感觉
return 1
说实话 Lua 程序时候经常 Ruby else
return n fact(n -1)*
从而一些细节差异中招例如定义不是 def end
而是 function 、then 不能省略调用函数参数周围 end
括号不能省略等等。 print(fact(6))
Lua
语法虽然看起来有点 Ruby ,内涵 1 计算阶乘 Lua 程序
JavaScript 。
例如对象列表区分对象系统
使用函数对象观察一下 Lua 行为觉得处处 JavaScript 影子
数据类型
作为通用脚本语言,Lua 可以操作下列数据类型
‰
数值
‰
字符串
‰
函数
‰

‰
用户自定义
其中数值字符串各种语言中都具备普遍数据类型值得注意,Lua 中的
数值全部浮点数没有整数其他语言如果特别声明的话),采用
Perl
相同设计函数我们稍后逐一讲解
用户 自定义类型 C 语言 Lua 进行扩展使用数据类型文件输入输出
功能是以用户自定义类型方式提供
149
----------------------- Page 161-----------------------
3 编程语言潮流
函数
Lua 函数字符串数值并列基本数据结构。Lua 函数属于第一对象
(first-class object ),
也就是说数值其他类型一样可以赋值变量作为参数传递以及
返回接收
获得作为函数需要使用指定名称 function 语句 2 指定名称
function
语句指定名称 function 语句创建函数进行赋值之后结果一样
Lua 通过限度利用函数对象实现面向对象编程功能关于 Lua
面向对象编程内容我们稍后进行讲解
--
作为数组
array = {1, 2, 3}
--Lua
数组索引1开始
print(array[1])--> 1
--
用来长度操作符 Perl)#
--
函数a定义 print( array) --> 3#
function a(n)
print(n) --
作为列表
end hash = {x = 1, y = 2, z = 3}
--
取出列表元素
--
赋值变量a print(hash['x']) --> 1
a(5) --> 5 --
取出列表元素结构风格
--
通过type(a)获取a数据类型 print(hash.y) --> 2
print(type(a)) --> function
--
数组列表共存
--
通过赋值语句定义函数 --通过赋值添加成员
b = function (n) array["x"] = 42
print(n) --
通过赋值添加成员结构风格
end array.y= 55
--
无论方式可以访问
--
可以a一样进行调用 print(array["x"]) --> 42
b(5) --> 5 print(array.x) --> 42
--
检查b类型 --列表元素长度包含长度
print(type(b)) --> function print( array) --> 3#
2 函数对象 3 编程

Lua
特色数据类型(table )。一种可以实现其他语言数组列表
对象所有这些功能万能数据类型。JavaScript 列表实现对象数组另外
实现方式因此 Lua 这种列表数组进行合并做法显得更加彻底这样数组
150
----------------------- Page 162-----------------------
3.5 Lua
列表合为一体语言除了 Lua 以外还有 PHP 。
关于数组列表合并到底是不是良好选择应该还有讨论余地至少
减少语言构成要素一点贡献由于 Lua 动态类型语言因此无论变量还是数组
元素可以存放任意类型数据
Lua
各种使用方法 3 需要注意有一点其他多语言一样
就是作为数组索引 1 开始的确以前 FORTRAN Pascal 数组索引
1 开始 C 语言之后最近语言索引基本上 0 开始因此容易搞错
特别如果小心添加索引 0 元素这个元素不是作为个数元素而是
0 列表素来建立一点容易中招
此外还有一点比较违背直觉就是 Lua 对表应用获取长度操作符“#”
包括整数索引数组元素中的列表元素包含长度一点
需要注意

Lua
本来不是设计一种面向对象语言因此面向对象功能通过(meta table )
这样一种非常怪异方式实现。Lua 并不直接支持面向对象言中常见对象方法
其中对象通过实现方法通过函数实现
首先我们来讲到底什么 Lua 用户自定义数据类型这样一种
设定这个设定通过 setmetatable() 函数执行对于用户自定义数据类型需要
操作产生操作相对事件针对各个事件进行处理根据事件类型
决定对于设定用户自定义类型,Lua 参照进行处理
例如执行元素引用 index 事件处理逻辑如下执行 table[key] 表达式
确认 table 实际还是用户自定义数据其他类型
如果 table 实际通过直接取出 key 相对元素使用 rawget 函数)。
如果存在元素元素即为事件执行结果
如果 key 对应元素存在取出 table table 取出 __index 这个
元素如果 __index 元素没有创建返回 nil 结果结束事件处理
table 不是实际 table 取出 __index 元素如果 __index 元素没有
151
----------------------- Page 163-----------------------
3 编程语言潮流
创建表示 table 支持 index 事件产生 --table[key]对于table[key]事件处理
错误。 function gettable_event(table, key)
local h
if type(table) == "table" then
如果 __index 函数 local v = rawget(table, key)
~
table
index 作为参数调用函数否则 if v = nil then return v end
h = metatable(table).__index
忽略 table ,直接元素这里假定 h ) if h == nil then return nil end
作为使用从中检索 key 对应元素。 else
h = metatable(table).__index
if h == nil then
如果上述逻辑 Lua 编写 出来就是 error(...)
4
这样基本上对于任何事件处理 end
end
归结下面逻辑: if type(h) == "function" then
return (h(table, key)) --call the
handler
‰
如果存在规定操作执行
else
‰
否则取出事件对应“__ ” return h[key] --or repeat operation
on it
开头 元素如果元素函数 end
函数。 end
‰
如果元素函数元素代替 4 index 事件处理
table
执行事件对应处理逻辑
Lua
处理事件一览 2
2 Lua事件
        
add
加法(+ ) 顺序搜索
sub
减法(- ) 同上
mul
乘法(* ) 同上
div
除法(/ ) 同上
mod
(% ) 同上
pow
(^ ) 同上
unm
单项减法(- ) —
concat
连接(.. ) 顺序搜索
len
长度(# ) —
eq
比较(== ) 数值字符串直接比较
lt
小于(< ) 大于(> )两侧对调
le
小于等于(<= ) 如果没有 __le 搜索 __lt
index
引用元素([] ) x[ “key”] x.key 都会触发事件
newindex
设定元素([]= ) 同上
call
函数调用
152
----------------------- Page 164-----------------------
3.5 Lua
方法调用实现
这么到底如何使用到底实现哪些面向对象编程好像还是明白
面向对象编程基本就是创建对象调用方法。Lua 原则上作为对象使用
因此创建对象没有任何问题关于调用方法如果元素函数对象的话可以直接调用
Lua
可以这样
obj.x(foo)
表示 obj 变量存放取出 x 视为函数进行调用这样一来
看上去其他面向对象语言中的方法调用一模一样
不过如果这种方式作为方法用来考虑的话面向对象还有几个问题
首先 ,obj.x 这种调用方式说到底只是 obj 属性 x 这个函数对象出来而已
大多数面向对象言中方法实体位于不是位于单独对象 JavaScript
基于原型语言 原型对象代替进行方法搜索因此单独对象并不
方法实体
因此 Lua 为了实现这样方法搜索机制需要使用 index 事件 4
说明一样只要设定 __index ,key 存在利用 __index 进行搜索
于是只要编写 5 这样 程序可以
proto = {
对象方法搜索转发其他对象。 x = function() print("x") end
}
w
这种情况,proto 变成原型对象, obj= {}
setmetatable(obj, {__index = proto})
obj obj.x()
proto 。
这样一来类似 JavaScript 这样基于原型 5 使用实现方法搜索
面向对象编程基础完成
不过这样还是问题通过方法搜索得到函数对象只是单纯函数无法获得
调用方法接收器相关信息于是过程数据发生分离无法顺利实现面向
对象功能
JavaScript
关于接收器信息可以通过 this 访问此外 Python 通过方法
调用形式获得并非单纯函数对象而是方法对象”,调用这个方法对象
内部作为第一参数附加函数调用过程
153
----------------------- Page 165-----------------------
3 编程语言潮流
那么,Lua 怎么办? Lua 准备一种支持方法调用语法糖(syntax sugar ,
目的引入语法形式)。 Lua 方法调用推荐使用单纯元素访问

obj.x()
而是推荐使用这样形式
obj:x()
就是 Lua 添加语法糖表示
obj.x(obj)
意思也就是说通过冒号记法调用函数接收器作为第一参数添加进来不过
表达式 obj 求值进行一次因此即便 obj 副作用发生一次冒号记法函数
调用如果还有其他参数的话相应顺次向后移动位置
也就是说 Python 通过使用方法对象实现接收器添加第一参数函数调用
Lua 通过采用一种特殊调用形式实现这样设计包含复杂机制能够
受到 Lua 简单哲学
这个语法糖方法定义有效 5 程序添加下面
function base:y(x)
print(self,x)
end
语法糖解释下面代码
base.y = function(self,x)
print(self)
end
从而 base 定义方法
Lua
进行方法调用特别需要注意一点就是冒号记法定义方法调用时候
必须使用冒号记法进行调用否则 self 相当参数不会添加进去参数顺序
错乱从而无意中成为产生错误原因尤其是其他语言方法调用属性获取大多
圆点记法因此容易混淆此外 Lua 传递函数参数个数差异并不
出错因此即便看见错误信息无法马上发现问题因为这个问题过招感到
苦恼
154
----------------------- Page 166-----------------------
3.5 Lua
基于原型编程
通过刚才讲解机制我们了解进行面向对象编程必需最低限度功能然而
实话这些功能不够好用为了更加接近其他语言我们少许加工一下
正如刚才方法调用通过使用语法糖可以毫无问题完成不足部分
现有对象扩展机制也就是说 Ruby 这样基于语言相当于继承功能
不过考虑 Lua 对象机制比起基于还是基于原型更加合适于是
准备支持基于原型面向对象编程简单 6 )。自己编写工具
即使详细了解 Lua 原始机制可以实现面向对象
--Object
所有对象上级 --允许类似基于编程用法
Object = {} function Object:new(...)
--
成为对象
--
创建现有对象副本方法 local object = {}
function Object:clone()
--
成为对象 --找不到方法搜索目标设定self
local object = {} setmetatable(object, {__index = self})
--
复制元素
for k,vin pairs(self) do --
Ruby一样初始化调用initialize方法
object[k] = v --(...)
表示所有参数原样传递
end object:initialize(...)
--
设定 return object
--
虽然名字clone不是复制而是自身转发 end
--
为了修改反映出来
setmetatable(object, {__index = self) --
实例初始化函数
function Object:initialize(...)
return object --
默认进行任何操作
end end
--
对象原型创建对象 --为了本来需要进行类似操作准备原型
--
对象通过 initialize方法进行初始化 Class = Object:new()
右上
6 Lua 面向对象工具
本来基于原型面向对象编程创建对象对于找不到方法转发目标
指定原型对象因此例如编写图表进行操作程序的话只要创建
代表性然后根据需要通过复制那个创建可以
然而大多数还是习惯基于面向对象编程因此实际上并不创建具有代表性
”,大多创建原型专用对象一样使用这个考虑到了一点
单独提供方法原型创建对象 new 方法另一创建对象副本 clone 方法
155
----------------------- Page 167-----------------------
3 编程语言潮流
clone
方法返回对象副本参照复制对象进行设定这里我们没有
单纯复制 原始对象如果修改需要修改部分副本对象反映出来
例如相当于对象添加方法的话我们希望拥有添加方法
另一方面,new 方法原型创建对象通过 initialize 方法进行初始化这里调用
initialize
方法方式参考 Ruby 设计
这个工具使用例如 7 一行程序实际功能参见注释
--
首先创建原型 function Point3D:initialize(x, y, z)
--
创建表示原型 Point -- 调用 initialize进行初始化
Point = Class:new() --
必须指定self有点美观
Point.initialize(self, x, y)
-- Point
实例初始化方法 -- Point3D扩展部分
--
设定坐标xy self.z = z
function Point:initialize(x, y) end
self.x = x
self.y = y -- Point3D
magnitude()方法
end function Point3D:magnitude()
return math.sqrt(self.x^2 + self.y^2 +
--
定义Point方法magnitude self.z^2)
--
原点之间距离 end
function Point:magnitude()
return math.sqrt(self.x^2 + self.y^2) --
创建 Point3D实例
end p3 = Point3D:new(1,2,3)
--
创建 Point实例 -- 属性检查
-- x = 3, y = 4 print("p3.x = ", p3.x) --> 1
p = Point:new(3,4) print("p3.y = ", p3.y) --> 2
print("p3.z = ", p3.z) --> 3
--
确认是否设定成员
print("p.x = ", p.x) --> p.x = 3 --
调用magnitude方法
print("p.y = ", p.y) --> p.x = 4 print(p3:magnitude()) --> 3.7416573867739
--
计算magnitude() -- 创建p3副本
--
勾股定理求得结果5 p4 = p3:clone()
print(p:magnitude()) --> 5 --
属性检查
print("p4.x = ", p4.x) --> 1
--
继承PointPoint3D定义 print("p4.y = ", p4.y) --> 2
--
为了创建进行clone操作 print("p4.z = ", p4.z) --> 3
Point3D = Point:clone()
--
调用magnitude方法结果相同
-- Point3D
对象初始化方法 print(p4:magnitude()) -->
--
由于变成三维空间因此增加z 3.7416573867739
右上
7 面向对象工具使用示例
156
----------------------- Page 168-----------------------
3.5 Lua
怎么样这个保留基于原型风格习惯基于容易使用
自夸感觉
Ruby 比较语言
嵌入方针进行设计 Lua ,默认状态真是简洁惊人标准状态除了基本
数据类型之外其他一概没有功能也就是文件输入输出字符串模板匹配操作
数学函数加上加载这些而已开始提供大量功能 Ruby 相比显得有些贫乏
正如之前,Lua 虽然能够进行面向对象编程进行编程仿佛感觉
对象看到五脏六腑一样 Perl 早期面向对象编程感觉差不多
话虽如此觉得虽然感觉有些不适实用角度实现面向对象编程还是没有
问题 虽然提供功能并非一无是处语言基本功能有一些不满例如
没有异常处理功能不过考虑一种应用程序扩展语言一点并非不能妥协
只能功能大小需要权衡不过,Lua 通过 C 语言方便添加功能默认
功能仅仅用来表达逻辑功能只要根据嵌入应用程序需求进行添加可以
同样是以嵌入方针 Tcl ,由于附带 GUI Tk 完成相当而且出乎作者意料
不是用于嵌入应用程序而是直接作为 GUI 语言使用不过 Lua 目前还是遵照
最初目的嵌入主要应用领域
嵌入语言 Lua
Lua
作为嵌入语言观察一下实现发现特点部分关于解释器数据
包括名为 lua_State 结构通过这样方式进程可以容纳多个解释器
例如游戏角色各自分配独立解释器只要不在意内存用量的话完全可以做到
此外线程环境通过线程分配解释器可以限度发挥性能
,Lua 垃圾回收讲究游戏之类实时要求环境不再使用
对象进行回收垃圾回收机制一直难题例如射击游戏如果由于垃圾回收使
1 秒钟无法操作自己飞机游戏变得没法嵌入环境虽然处理性能非常
重要响应速度更加重要。Lua 通过使用增量垃圾回收算法使得程序暂停时间得到
缩短
157
----------------------- Page 169-----------------------
3 编程语言潮流
高速响应特色 Lua ,用于嵌入各种应用程序例如网络游戏
世界》(World of Warcraft )、《仙境传说》(Ragnarok Online ),以及美国Adobe Systems 公司
图像处理软件“Adobe Photoshop Lightroom”也许出于这个原因,Adobe Lua
提供赞助
比较罕见,Yamaha 路由器 RTX 1200 嵌入 Lua 。以前美国 Cisco Systems
路由器曾经嵌入 Tcl , Lua 似乎成为最近流行趋势
除了上述这些以外嵌入 Lua 游戏、Web 服务器工具软件应用程序已然不计其数
Ruby 比较实现
如果嵌入应用程序角度比较一下 Ruby Lua 的话不能否认 Ruby 处在稍许
不利地位
Ruby
原本作为独立通用语言追求功能易用性目标设计嵌入其他
程序虽说不是不可能并非十分擅长尤其对于缺少用于表现解释器结构
缺陷出于这个原因进程只能容纳解释器 Lua 容易做到
多个解释器协调线程分配 Ruby 非常困难
叫做 MVM (Multiple VM )补丁可以解决这个问题不过引入标准实现
需要一些时间
当然嵌入 Ruby 应用程序并非没有例如 Enterbrain 公司发售软件《RPG
制作大师》① 嵌入一种叫做 RGSS (RubyGame Scripting System )RPG 编程工具其实
就是 Ruby 1.8。
此外听到比较古老报告就是英国酒吧放置“World Cup Trivia”
嵌入 Ruby 。
语言优劣并不是非语言本身功能以及易用性方面 Ruby 更加优秀
足够信心如果涉及嵌入应用程序,Lua 实现方面实力一点不能
否认
① RPG
制作大师(RPG Maker )Enterbrain 公司发售 RPG (角色扮演游戏制作工具1990 开始
这个系列已经不同平台推出版本,RGSS 诞生加入 2004 “RPG 制作大师 XP”
版本开始
158
----------------------- Page 170-----------------------
3.5 Lua
嵌入 Ruby
不过伴随计算机性能提升嵌入领域,CPU 性能内存容量指标以前
大幅度改善控制组件性能已经相当于不久之前 PC ,便是游戏机性能
PC 不相上下可以作为电脑使用手机性能容量不断增加
结果就是所谓嵌入领域中的软件复杂性以前不可同日而语如今软件
开发效率嵌入领域为了不可回避问题因此为了提高软件开发效率其中
有力工具就是采用高级编程语言
为了这个目的预计能够嵌入领域身手 Ruby 引擎开发项目已经采纳
日本经济产业 2010 年度地域创新研究开发事业”。这个项目开发具备丰富嵌入
各界人士共同进行核心部分开发工作完成这个轻型 Ruby 基于 MIT 授权协议
进行开源通过“mruby”这个名称便可以获得源代码大家访问 https://github.com/mruby/
mruby 。
这个轻型 Ruby 用来替代现有 Ruby ,而是嵌入领域中心现在 Ruby
(CRuby )
补充 JRuby 面向 JVM 平台 Ruby 语言可能性扩展一样
轻型 Ruby 面向嵌入领域 Ruby 可能性又一次扩展
随着嵌入计算机性能提升软件复杂化逐步推进 Lua 这样规模引擎
面向应用程序嵌入语言今后舞台应该越来越广阔
159
----------------------- Page 171-----------------------
3 编程语言潮流
编程语言潮流后记
我们着重介绍一些原稿时候比较新颖语言世界到底
多少编程语言具体数字知道个人兴趣制作或者学者撰写论文
一部分开发语言的话恐怕真的不计其数实际上上学所属研究
几年时间开发语言记得自己毕业论文编程语言不是
Ruby )
设计实现相关
这样背景诞生语言大部分随着作者毕业工作或者随着兴趣减弱
各种理由开发逐渐停滞然后慢慢消亡这样无数尸体出现
异类它们变得广为人得以长期存在下去
这样诞生语言应用过程不断进化几年具有开创性莫过于
v8
LuaJIT 出现
v8
Google Chrome 浏览器搭载 JavaScript 引擎,LuaJIT 介绍面向
嵌入环境脚本语言 Lua 高速实现两者作为动态语言拥有惊人速度特点
速度甚至超越静态类型语言擅长编译引擎性能 编程语言实现
在内一直以来由于没有编译利用类型信息加上语言性质动态
因此高速实现困难约等于不可能作为借口现实中超高速引擎已经出现
再也谈不上什么不可能今后以此为基础大家开始一轮进步实际上
各种浏览器 JavaScript 性能几年获得飞跃提高已然进入竞争
时代
今后语言需要追求兼具动态语言灵活性编译语言高速介绍
Dart ,
诞生背景在于 Dart 这样采用强制类型信息还是某种静态
语言一样采用类型推导正是语言进化方向有意思地方
几年酝酿关于语言一些构思例如 Ruby 一样动态语言局部
变量无法再次赋值单一赋值),提供数据结构基本上不可改变(immutable )这些
函数语言尤其是 Erlang )特征如果Ruby 这样面向对象功能结合起来
是不是形成一种容易调试容易发挥性能语言由于自己忙于
Ruby ,没有精力着手开发语言各位读者之中有没有想要挑战一下
160
----------------------- Page 172-----------------------
3.5 Lua
云计算时代编程 4
161
----------------------- Page 173-----------------------
4.1
扩展
根据美国加州大学伯克利分校名为“How Much Information?”调查结果
2002
人类创造数据总量超过 5 字节(EB )。其中(Exa , 10 18 次方
或者 2 60 次方前缀这类前缀还有顺序分别(Kilo ,10 3 次方)、(Mega ,
10
6 次方)、(Giga ,10 9 次方)、(Tera ,10 12 次方)、(Peta ,10 15 次方)、
(Exa ,10 18 次方)。
此外根据调查做出预测,2006 人类信息总量达到 161EB,2010 达到
988EB (
约等于1ZB,Z Zetta , 10 21 次方字节)。意味着人类 1 年内产生
数据已经超过截止 20 世纪末人类创造全部信息总量
如此大量信息创造流通记录称为信息爆炸生活 21 世纪我们每天
必须处理如此庞大信息
信息爆炸并不仅仅社会整体面临问题我们每个人拥有数据每天不断增加
接触计算机 20 世纪 80 年代初存储媒体一般采用 5 英寸软盘面对 320KB
容量”,当时还是初中生曾经感叹这些数据容量恐怕一辈子
然而 20 多年 以后使用电脑硬盘容量已经 160GB 相当于 5 英寸
50 更为恐怖这些容量 8 成都已经各种各样数据填满刚刚
一下手头保存电子邮件压缩之后足足 3.7GB 这些邮件每天不断
增加
信息尺度
物理学世界随着尺度变化物体行为发生变化量子力学支配
原子粒子世界以及银河这样天文学世界都会发生一些我们身边绝对不到
现象
粒子世界粒子存在位置无法明确观测只能概率论描述据说
因为观测粒子必须通过也就是光子这种粒子其他粒子反射才能完成
163
----------------------- Page 174-----------------------
4 云计算时代编程
这种反射干扰观测粒子在下一瞬间位置
不仅如此量子力学世界仿佛可以无视质量守恒定律一样发生一些神奇现象
比如一无所有地方产生粒子或者粒子类似瞬间移动方式穿过毫无缝隙墙壁
真是常识汇演
天文世界一样两端相距亿光年银河星团以及由于引力无法
黑洞这些东西日常感觉想象
这些常理现象发生因为到了一些平常我们留心数值影响例如光速
原子粒子大小时间尺度它们影响无法忽略
IT 世界发生同样事情从小尺度上来电路精密化导致量子力学影响
显现从而影响摩尔定律尺度上来产生信息爆炸导致海量数据问题
不同计算机不会感到疲劳厌烦无论需要多少时间最终能够完成任务然而
如果无法现实时间范围得出结果毫无用处数据量变得很出现
以前从来没有考虑各种问题对于这些问题对策必须仔细考量
下面我们容易理解例子看一看关于数据保存查找问题
大量数据查找
所谓查找就是数据找出满足
O (n ・log n )
条件对象简单数据查找算法
O (n2 )
线性查找所谓线性查找其实并不
只要逐一取出数据检查是否满足 O (n )
可以叫做一种算法好像
O (log n )
确实夸张一些。 O (1 )
数据
线性查找计算 O n( ) ,
1 算法计算性能差异
查找对象数据正比
2
算法性能还有属于 O n( ) 、 (O n ·log ) 数量级相比之下n O n( ) 算是 1 )。
这里记法称为 O 记法或者 O 记法),计算参数 n(大多数情况输入数据变化情况

164
----------------------- Page 175-----------------------
4.1 
扩展
即便如此随着数据增加查找所需时间随之不断延长假设 4MB 数据进行
查找需要 0.5 那么 4GB (=4000MB ① )查找计算需要8 20 这个时间已经
比较难以忍受如果 4TB (=4000GB )数据单纯计算时间差不多需要6
Google 搜索引擎搜索数据早已超过 TB 到了 PB 因此明显
采用单纯线性查找无法实现那么对于这样信息爆炸到底应该如何应对
二分查找
经验计算性能方面问题只能算法解决因为变更只能带来
分之百分之改善而已
这里我们介绍一些一定前提条件可以大地改善查找计算算法借此
学习应对信息爆炸算法方面思考方式
对于没有任何前提条件查找线性查找几乎唯一算法实际上大多数情况
数据查找条件中都存在一定前提利用这些前提条件有一些算法可以计算大幅
减少首先我们介绍一种基本查找算法——二分查找(binary search )。
使用二分查找前提条件数据之间存在大小关系已经按照大小关系排序利用
性质查找计算可以下降 O(log ) 。n
线性查找大多数从头开始二分查找中间开始查找首先将要查找
对象数据正好位于中点数据进行比较结果可能两者相等查找对象较大
找对象
如果相等表示已经找到查找结束否则需要继续查找由于前提条件
已经按照大小顺序进行排序因此如果查找对象数据中点数据数据一定
位于较大一半反之一定位于小的一半通过一次比较可以查找范围缩小
原来一半这种积极缩小查找范围做法就是缩减计算诀窍
这个算法 Ruby 编写出来 2 2 定义方法接受已经排序数组 data ,
数值 value。如果 value data 存在的话返回 data 中的元素位置索引如果
存在返回 nil 。
关于 K 、M 、G 表示数量级前缀 1000 递增十进制 1024 递增二进制算法表示硬盘
容量场合 看起来一般采用十进制因此文章采用十进制其实如果明确表示
1024
递增二进制方式另外 Ki 、Mi 、Gi 前缀“320KiB”,这种写法一般常见。(
165
----------------------- Page 176-----------------------
4 云计算时代编程
二分查找计算 n (= def bsearch (data, value)
个数小时差异不大 随着 lo = 0
hi = data.size
n
增大差异变得越来越。 while lo < hi
mid = (lo + hi) / 2 Note: bug in #
1 显示随着数据个数 Programming Pearl
if data[mid] < value
,log n 增加趋势只有 10 lo = mid + 1;
else
数据,n log n 差异 4.3
hi = mid;
100 数据差异 end
end
到了 72000 。 if lo < data.size && data[lo] == value
lo found#
出人意料, else
nil not found#
二分查找实现并非一帆风顺。 end
例如,1986 出版 Jon Bentley end

编程》 (Programming 2 二分查找程序
Pearls )
介绍二分
算法虽然示例程序存在 bug ,直到2006 包括作者自己在内竟然没有任何注意
1  O n( )O(log )计算变化n
n log n n / log (
)n
10 2.302585092994046 4.3429448190325175
100 4.605170185988092 21.71472409516259
1000 6.907755278982137 144.76482730108395
10000 9.210340371976184 1085.7362047581294
100000 11.512925464970229 8685.889638065037
1000000 13.815510557964274 72382.41365054197
这个 bug 位于 2 5 Note 注释所在地方。《编程原始程序 C
编写 C 这样语言,lo hi 有可能超过正整数这样 bug 称为
整数溢出(integer overflow )。
因此 C 语言这个部分应该
mid = lo + ((hi - lo) / 2)
防止溢出 1986 计算机索引超过整数情况非常少见因此
时间没有注意这个 bug 。
① 《
编程2 译本人民邮电出版社 2008 出版
166
----------------------- Page 177-----------------------
4.1 
扩展
再说,Ruby 没有整数这个概念非常整数
转换整数因此 2 Ruby 程序没有这样 bug 。
列表
计算角度理想数据结构就是列表列表表达对象另一对象
映射数据结构。Ruby 一种名为 Hash 内建数据结构就是列表概念上来
由于一种采用数值索引数组因此称为联想数组”, Ruby (Perl 一样
内部实现称为 Hash 。相应,Smalltalk Python 相当于 Hash 数据结构称为
字典(Dictionary )。
列表采用一种巧妙查找方式平均查找计算数据无关也就是说
O 记法表示的话就是 O(1)。无论数据如何增大访问其中数据需要固定时间
因此已经算是登峰造极理论上来
列表需要准备散列函数”,用于各个计算成为称为散列整数
散列函数需要满足以下性质
‰
数据数值(0 ~N - 1 )映射
‰
足够分散
‰
不易冲突
足够分散即便数据只有小的差异散列函数计算结果需要差异。“
冲突不易发生不同数据得到相同散列情况
存在这样散列函数
hashtable = [nil] N *
根据元素数量创建数组
简单列表可以通过
散列索引数组表现 def hash_set(hashtable, x, y) 数据存放散列
hashtable[hash(x)] = y
作为索引存入
3 )。 end
def hash_get(hashtable, x)
数据取出散列
由于散列计算指定 hashtable[hash(x)] 作为索引取出
索引访问数组元素所需时间 end
数据个数无关因此可以得出 3 简单列表
列表访问计算 O(1)。
不过世界没有这么简单事情 3 这样单纯散列根本不够实用作为实用
列表必须能够应对 3 列表没有考虑问题散列冲突和数溢出
167
----------------------- Page 178-----------------------
4 云计算时代编程
虽然散列函数数据散列映射并不保证这个映射一对一关系因此不同
数据有可能得到相同散列这样不同数据拥有相同散列情况称为冲突”。
作为实用列表必须能够应对散列冲突
列表实现应对冲突方法大体上可以分为地址(chaining )开放地址
(open addressing )
地址拥有相同散列元素存放链表因此随着元素
增加散列冲突查询链表时间跟着增加造成性能损失
不过后面讲到开放地址相比这种方法优点元素删除可以比较简单
高性能方式实现因此 Ruby Hash 采用这种地址
另一方面开放地址法则遇到冲突继续寻找数据存放空间一般称为
)。寻找空闲简单方法就是顺序遍历直到找到空闲为止一般来说这样
方法太过简单实际上进行复杂一些计算。Python 字典就是采用这种开放

开放地址访问数据如果散列代表位置存在希望数据
要么代表数据存在要么代表由于散列冲突转存别的于是可以通过下列
算法寻找目标
(1)
计算数据(key )散列
(2)
散列找到相应如果散列数量余数
(3)
如果数据一致使用查找结束
(4)
如果空闲列表存在数据查找结束
(5)
计算下一个位置
(6)
返回 3 进行循环
由于开放地址数据存放使用相对普通数组方式链表相比所需内存空间
因此性能存在有利一面
不过这种方法不是尽善尽美缺点首先相比原本散列冲突发生率
散列冲突发生更加频繁因为开发地址冲突数据存放下一个
意味着下一个无法用来存放原本散列直接对应数据
存放数据数组逐渐填满这样冲突频繁发生一旦发生冲突
通过计算求得下一个位置用于查找处理时间逐渐增加因此开放地址
设计使用数组大小必须留有一定余量
其次数据删除比较麻烦由于开放地址一般元素冲突不在原位元素
168
----------------------- Page 179-----------------------
4.1 
扩展
在一起因此无法简单删除数据删除数据仅仅删除对象数据所在
空闲不够
这样一来开放地址中的连锁可能切断从而导致本来存在数据无法找到
删除数据必须存放元素设定一种特殊状态空闲允许存放数据
中断下一个计算”。
随着列表存放数据越来越发生冲突危险随之增加假设真的存在一种
散列函数对于任何数据完全不同散列① ,那么元素个数超过列表
个数不可避免产生冲突尤其是开放地址快要填满引发冲突
更加显著
无论采用方法一旦发生冲突必须沿着某种连锁寻找数据因此无法实现 O(1)
查找效率
因此实用列表实现冲突查找效率产生不利影响超过程度
对表大小进行修改从而努力平均水平保持 O(1) 查找效率例如采用地址
Ruby
Hash 冲突产生链表长度超过 5 增加列表列表
进行重组另外采用开放地址 Python 分之填满进行重组
即便最好情况重组操作所需计算至少元素个数相关 O n( ) ),不过
只要重组频度尽量控制小的可以列表平均访问消耗水平维持 O(1)。
列表通过使用散列函数避免线性查找从而使得计算大幅度减少真是一种巧妙
算法
过滤器
下面我们介绍一种运用散列函数有趣数据结构——过滤器(Bloom filter )。
过滤器一种可以判断数据是否存在数据结构或者可以判断集合
包含成员数据结构过滤器特点如下
‰
判断时间数据个数无关(O(1) )
‰
空间效率非常
这样理想散列函数称为完美散列函数如果事先得知数据范围构造完美散列函数可能。(

169
----------------------- Page 180-----------------------
4 云计算时代编程
‰
无法删除元素
‰
偶尔出错(!)
偶尔出错貌似违背我们关于数据结构常识不过面对大量数据我们
目的缩小查找范围因此大多数情况少量误判并不产生什么问题
此外过滤器误判假阳性(false positive ),也就是说属于集合
判断属于集合不会产生假阴性(false negative )误判过滤器这样偶尔
出错算法称为概率算法(probabilistic algorithm )。
过滤器不但拥有时间效率(O(1) ),拥有空间效率理论假设
1% ),平均数据需要9.6 比特空间包括列表在内其他表示集合数据
中都需要包含原始数据相比之下这样空间效率简直压倒性
过滤器使用 k 散列函数 m 比特比特数组(bit array )。作为比特数组初始
所有比特 0。过滤器插入数据数据求得 k 散列大于0 小于 m ),
散列索引对应比特数组中的比特全部 1。
判断过滤器是否包含数据求得数据 k 散列只要对应比特
任意 0 ,可以判断集合包含数据
即便所有 k 比特 1,可能由于散列冲突导致偶然现象而已这种情况
假阳性 阳性发生概率集合中的数据个数 n 、散列函数种类 k ,以及比特数组
m 有关如果 m 对于 n 发生比特数组所有 1,从而所有数据判定
阳性情况
此外 k 数据消耗比特随之增加比特数组填充速度加快
引发误判相反 k 小时比特数组填充速度由于散列冲突增多导致
误判增加
信息爆炸引发大规模数据处理过滤器这样算法应该变得愈发重要
计算机极限
刚才我们介绍二分查找列表过滤器为了控制计算从而现实
时间完成大量数据处理算法
然而仅仅实现这些算法不足应对真正信息爆炸因为信息爆炸产生数据
170
----------------------- Page 181-----------------------
4.1 
扩展
规模不可能计算机完成处理最近一般电脑搭载
容量也就是 TB ,内存也就是 8GB 左右
摩尔定律恩泽虽然这样容量已然今非昔比 TB 容量完成 PB
数据实时处理还是完全不够
怎么办我们需要计算机数据计算分割进行处理计算机无法
处理数据如果 100 、1000 甚至 1 计算机进行合作可以现实时间
完成处理幸运计算机价格越来越便宜它们连接起来网络带宽越来越
越来越便宜。Google 公司为了提供搜索服务动用几个十万 PC 互相连接起来
构成数据中心。“这个诞生反映这种计算机实现分布式计算重要性
越来越
然而数万计算机构成高度分布式环境如何高效进行大量数据保存处理
没有得到普及因为现实能够拥有如此大量计算机构成计算环境
Google 屈指可数大公司而已
假设真的拥有大量计算机不能完全解决问题安装大量计算机大规模数据
心中最少每天都会计算机发生故障也就是说各种分布式处理必须考虑
计算机故障导致处理中断可能性计算机运行软件考虑
要素结果就是相比包含分布式计算程序开发高度分布式编程难度高出许多
DHT (
分布式列表
分布式环境工作列表称为 DHT (Distributed Hash Table ,分布式列表)。DHT
并非一种特定算法而是列表分布式环境进行实现技术统称实现
DHT
算法包括 CAN 、Chord、Pastry 、Tapestry
DHT
算法非常复杂这种复杂性原因分布式环境尤其是 P2P ①环境实现
列表遇到以下问题
‰
由于机器故障原因导致节点消失
‰
节点复原添加
‰
伴随上述问题产生数据位置查找路由困难问题
因此基本上数据都会副本进行保存此外为了应对节点增减需要重新计算
① P2P ,
Peer-to-Peer (点对点缩写一种中央服务器对等通信架构
171
----------------------- Page 182-----------------------
4 云计算时代编程
数据位置
近年来运用 DHT 技术分布式环境实现关系数据库 - 存储(key-value
store )
数据库受到越来越关注- 存储例子包括 CouchDB、TokyoTyrant 、Kai 、Roma
简单这些数据库通过网络进行访问 Hash ,数据分别存放计算机
各自针对数据规模网络架构实现语言方面特点
关于分布式环境数据存储除了 - 存储以外还有 GFS (Google File System )
这样分布式文件系统技术。GFS 后面讲到 MapReduce 基础
GFS
不是开源只能 Google 公司内部使用基本技术已经论文形式开发
基于论文提供信息 出现一般认为 GFS 具备同等功能开源软件“HFS”
(Hadoop File System )。
Roma
作为 - 存储数据库例子 介绍参与开发 Roma。Roma (Rakuten On-
Memory Architecture )
乐天技术研究所开发- 存储数据库乐天公司内部满足
分布式数据存储需求诞生特点如下
‰
所有数据存放在内中的内存数据库(In-Memory Database ,IMDB )
‰
采用环状分布式架构
‰
数据冗余所有数据除了本身之外各自拥有副本
‰
运行自由增减节点
‰
开源形式发布
Roma
计算机构成这些节点配置形成虚拟环状结构 4 )。这种
环状结构想到罗马竞技场正是 Roma 这个名字由来
0.0 1.0
客户端需要 Roma 存储
- 首先根据 散列4.2
12.2 4.0
数据 散列。Roma 中的
客户端
散列浮点数 圆环
结构节点划定 10.0 4.5
负责散列范围客户端
根据散列找出应该存放数据 6.8 5.1 4 Roma 架构
172
----------------------- Page 183-----------------------
4.1 
扩展
节点节点请求存储对应由于节点选择通过散列函数计算因此
计算固定
Roma
一定数据进行冗余所以数据入时节点两边相邻节点
数据副本请求因此对于所有数据都会负责节点以及相邻节点总共
保存副本
Roma
数据基本上存在各个节点内存中的为了避免数据丢失适当时机
文件形式输出快照万一遇到 Roma 系统整体紧急关闭情况通过快照数据日志
可以恢复所有数据数据取出同样通过计算散列找到相应节点节点
请求
对于 Roma 这样 分布式 - 存储数据库难题在于节点增减大量
计算机构成系统必须时刻考虑发生故障情况此外有时候为了应对数据访问
急剧增加考虑系统工作状态增加节点
故障原因导致节点减少情况一直保持联系相邻节点注意这个变化
环状结构进行重组首先消失节点负责散列范围两端相邻节点承担然后
节点减少导致有些数据副本减少因此这些数据需要进行搬运以便保证副本

增加节点处理方法相同节点圆环地方插入分配散列负责
范围属于范围数据两端相邻节点获取副本状态便稳定下来
假设由于网络状况不佳导致节点暂时无法访问由于数据无法正常复制可能
数据副本无法保持一致性问题实际上,Roma 中的所有数据通过一种时间戳记录
最后更新时间复制数据之间发生冲突各自时间戳必然不同这时时间戳
副本为准
Roma
优点在于容易维护只要系统搭建节点添加删除非常简单根据所需
容量增加节点十分方便
MapReduce
数据存储问题通过 - 存储分布式文件系统一定程度可以得到解决但是
高度分布式环境进行编程依然十分困难分布式列表我们已经接触到了解决
多个进程启动相互同步并发控制机器故障应对等分环境特有课题程序
173
----------------------- Page 184-----------------------
4 云计算时代编程
Google def map (name, document ) 接收文档
MapReduce
技术 分布 name: document name 分割单词#
document: document contents#
处理描述高效。MapReduce for word in document
数据处理通过 Map (数据映射)、 EmitIntermediate (word, 1 )
end
Reduce (
映射数据组合 end
进行描述
def reduce(word, values )
单词进行统计
key: a word #
返回合计
5 MapReduce 统计文档 values: a list of counts of the word#
result = 0
单词出现次数程序概念)。 for v in values
驱动这样过程需要相应 result += v.to_i
end
不过这里没有限定某种特定 Emit (result)
中间件。 end
5 MapReduce 编写单词计数程序概念
根据 5 这样程序,MapReduce
进行如下处理
‰
文档传递 map 函数
‰
单词进行统计结果传递 reduce 函数
MapReduce
程序高度抽象分配执行 Map 处理数据接近最优节点
发生错误异常情况进行应对工作可以实现高度自动化对于错误应对显得
尤其重要混入数据情况对象数据如果高达亿的话检查

MapReduce 发生错误数据处理进行重试如果依然发生错误的话
自动进行最佳应对”,比如忽略数据
GFS 一样,MapReduce 没有开源基于 Google 发表论文信息出现 Hadoop
这样开源软件 Google 赞助面向大学生高度分布式环境编程讲座使用
Hadoop 。
小结
随着信息爆炸计算机日用品分布式编程已经我们近在咫尺目前软件架构
可以不能完全应对这种格局变化软件层面依然需要进化
174
----------------------- Page 185-----------------------
4.2 C10K
问题
几年我去参加驾照更新讲座① ,讲师大叔三令五申开车不要想当然”。所谓开车
想当然”,就是主观想当然心态开车比如认为那个路口不会出来”、“
行人车道前面停下来之类就是我们 2-5 正常化偏见例子
作为对策我们应该提倡这样开车方式提醒自己那个路口可能出来”、“行人
突然出来
编程发生完全相同状况比如这个数据不会超过 16 比特范围”、“这个
不会公元 2000 以后这种想法正是导致 10 年前千年问题根源人类这种
生物仿佛诞生抱有自己有利主观看法便是现在世界依然因为想当然
编程不断引发各种各样 bug ,包括自己在内真是人头
何为 C10K 问题
C10K
问题可能这种想当然编程副产品所谓 C10K 问题就是 Client 10000
Problem ,
同时连接服务器客户端数量超过 10000 环境即便硬件性能足够
依然无法正常提供服务这样问题
这个问题发生背景主要背景如下
‰
由于互联网普及导致连接客户端数量增加
‰ keep-alive
连接保持技术普及
前者纯粹因为互联网用户数量增加导致热门网站访问者增加意味着连接
上限增加
问题在于后者使用(socket )网络连接不能忽视第一次建立连接
需要开销 HTTP 访 如果小数据传请求每次进行连接
日本更新驾照有效期必须参加相应交通讲座根据驾驶有效期时间有无违反交通法规
情况讲座分为不同类型时间 30 分钟 2 小时不等
175
----------------------- Page 186-----------------------
4 云计算时代编程
访问增加反复连接需要开销相当
为了避免这种浪费 HTTP1.1 开始同一服务器产生多个请求通过相同
连接完成就是 keep-alive 技术
近年来网络聊天室应用为了提高实时出现一种技术通过利用 keep-
alive
保持服务器客户端推送消息 Comet ,这样技术往往需要
连接
Comet 客户端服务器发起请求收到服务器响应显示页面之后
JavaScript
手段监听发送过来数据此后发生聊天室消息之类事件
服务器所有客户端一起发送响应数据 1 )。
发言
抓取
客户端 1 服务器
客户端定期服务器发起轮询
发言?)
客户端 2 客户端3
发言
推送 客户端 1 服务器
服务器通知客户端发言
1 网络聊天室为例对比 客户端 2 客户端3
抓取推送
以往 HTTP 聊天应用抓取方式实现用户发言、“按下刷新按钮
或者每隔一定时间触发条件客户端服务器进行轮询这种方式缺点
中的其他发言不会马上反映客户端上因此缺乏实时
相对,Comet 比较简单方式实现实时推送服务但是缺点
并发连接服务器造成负荷 Comet 提供服务情况抓取方式
遇到 C10K 问题从而导致服务缺乏扩展。Comet 可以是以扩展代价换取实时
一种做法
176
----------------------- Page 187-----------------------
4.2 C10K
问题
C10K
问题引发想当然
安全领域连接”① (Weakest link )说法如果两端用力
连接组成锁链其中脆弱连接因此锁链整体强度取决于其中
安全问题一样整体强度取决于其中脆弱部分
C10K
问题情况相似由于服务器同时应付超过并发连接情况以前
从未设想因此实际运作起来遇到想当然编程引发结果构成服务
要素哪怕只有要素没有考虑超过客户端的情况这个要素成为连接”,
从而导致问题发生
下面我们看看引发 C10K 问题元凶——历史上一些著名想当然
同时工作进程不会那么
出于历史原因 ,UNIX 进程 ID 符号 16 整数也就是说计算机
存在进程无法超过 32767 各种服务运行需要创建一些后台进程因此
程序可以创建进程数量这个数字一些
不过现在 16 整数作为进程 ID 操作系统越来越比如手边 Linux 系统
符号 32 整数作为进程 ID
虽然 数据类型带来进程上限几乎不存在不过允许无限创建进程带来
危害② ,因此进程上限可以在内参数进行设置一下手边 Linux 系统
进程上限设定 48353 。
现代操作系统进程上限在内参数设置我们后面内存开销
问题提到如果进程随着并发连接比例增加的话无法处理大量并发连接
时候需要事件驱动模型(event driven model )软件架构层面优化
而且,Linux 系统中的进程上限实际上意味着整个系统运行线程上限
并发连接启动线程程序存在同样上限
内存容量足够用来处理创建进程线程数量
① Weakest link
中文一般称为短板效应”,木板围城木桶多少取决于其中
一块木板长度
如果允许无限制创建进程的话那些能够不断产生进程程序带来持续扩大危害。(
177
----------------------- Page 188-----------------------
4 云计算时代编程
进程线程创建需要消耗一定内存如果程序连接分配进程
线程的话状态管理可以相对简化程序比较易懂问题在于内存开销
虽然程序空间可以通过操作系统功能进行共享变量空间空间无法共享
部分内存开销无法避免此外每次创建线程作为空间一般产生
1MB
2MB 左右内存开销
当然操作系统具备虚拟内存功能即便分配计算机安装内存物理内存
空间不会立刻造成停止响应然而超出物理内存部分访问速度
只有 DRAM 之一左右磁盘因此一旦分配内存超过物理内存容量性能
难以置信明显下滑
大量进程导致内存开销超过物理内存容量每次进行进程切换不得不产生磁盘访
这样一来消耗时间导致操作系统整体陷入一种几乎停止响应状态这样情况
称为抖动(thrashing )。
不过计算机安装内存容量不断攀升几年服务器配备 2GB 左右内存
常见做法现在一般服务器配置 8GB 内存不算罕见随着操作系统 64
快速发展也许不久将来并发连接分配进程线程简单模型足够
客户端到了那个时候说不定产生 C1000K 问题之类情况
同时打开文件描述数量不会那么
所谓文件描述 (file descriptor ),就是用来表示输入输出对象整数例如打开文件
网络通信文件描述数量限制 Linux 默认状态
所能打开文件描述数量 1024
如果程序结构需要进程文件描述进行操作就要考虑系统对于文件
描述数量限制根据需要必须设置改为默认 1024
UNIX 操作系统单个进程限制可以通过 setrlimit 系统调用进行设置系统全局
上限可以设置设置方法操作系统
或者我们可以考虑这样一种方式 1000 并发连接分配进程这样一来
连接只要 10 进程即便使用默认设置不会到达文件描述上限
多个文件描述进行监视 select 系统调用足够
正如上面,“连接对应进程 / 线程这样程序虽然简单在内开销
方面存在问题于是我们需要进程使用单独线程处理多个连接这种情况
178
----------------------- Page 189-----------------------
4.2 C10K
问题
如果任何检查直接进行读取的话等待收到数据过程程序整体
运行中断
线程处理多个连接时候这种等待输入运行中断称为阻塞致命
为了避免阻塞读取数据必须检查文件描述中的输入是否已经到达
于是 UNIX 操作系统多个文件描述可以使用叫做 select 系统调用
监视它们是否处于读写状态。select 系统调用将要监视文件描述存入 fd_set
设定超时时间它们状态进行监视指定文件描述变为可读
发生异常状态或者经过指定超时时间调用返回之后通过检查 fd_set ,
得知指定文件描述之中发生怎样状态变化 2 )。
#define NSOCKS 2
int sock[NSOCKS], maxfd; sock[1]、sock[2]……
存入监视socket。
fd_set readfds; maxfd
存入文件描述
struct timeval tv;
int i, n;
FD_ZERO(&readfds); fd_set
初始化
for (i=0; i<NSOCKS; i++) {
FD_SET(sock[i], &readfds);
}
tv.tv_sec = 2; 2
超时
tv.tv_usec = 0;
n = select(maxfd+1, &readfds, NULL, NULL, &tv);
调用select,监视read。
关于返回n:负数出错,0–超时
if (n < 0) { /
出错 / * * 正数状态发生变化fd数量
perror(NULL);
exit(0);
}
if (n == 0) { /
超时 /* *
puts("timeout");
exit(0);
}
else { /
成功 /* *
for (i=0; i<NSOCKS; i++) {
if (FD_ISSET(sock[i], &fds)) {
do_something(sock[i]);
}
}
}
---
2 select 系统调用使用示例节选
然而如果考虑发生 C10K 问题这样需要处理大量并发连接情况使用 select 系统
存在一些问题首先,select 系统调用能够监视文件描述数量上限这个上限
179
----------------------- Page 190-----------------------
4 云计算时代编程
定义 FD_SETSIZE 虽然操作系统一般 1024 左右即便通过 setrlimit
提高进程中的文件描述上限并不意味着 select 系统调用限制能够得到改善
一点特别需要注意
select
系统调用另一问题在于调用 select 作为参数 fd_set 结构体会修改
select
系统调用通过 fd_set 结构接收监视文件描述为了标记实际上发生状态变化
文件描述相应 fd_set 进行改写于是为了通过 fd_set 得知到底哪些文件描述
处于状态必须每次监视中的文件描述全部检查一遍
虽然单独每次开销通过 select 系统调用进行监视操作非常频繁需要
文件描述越来越多时这种小开累积起来引发问题
为了避免这样问题可能遇到 C10K 问题应用程序尽量使用 select 系统调用
为此可以使用 epoll 、kqueue 其他用于监视文件描述 API ,或者可以使用
阻塞 I/O。或者可以刻意避免使用 select 系统调用而是进程处理连接
控制 select 上限以下
使用 epoll 功能
遗憾如果通过 select 系统用来实现多个文件描述监视那么各种操作系统
没有统一方法例如 FreeBSD 系统 kqueue ,Solariszh /dev/poll ,Linux
称为 epoll 功能这些功能全都介绍一遍实在我们看看 Linux
epoll 这个功能
epoll
epoll_create、epoll_ctl epoll_wait 系统调用构成用法
epoll_create 系统调用创建监视描述描述用于代表监视文件描述然后通过
epoll_ctl
监视描述进行注册通过 epoll_wait 进行实际监视运用 epoll 程序节选
3 select 系统调用相比,epoll 优点如下
‰
监视 fd 数量没有限制
‰
内核记忆监视 fd ,无需每次进行初始化
‰
返回产生事件 fd 信息因此无需遍历所有 fd
通过这样机制使得无谓复制循环操作大幅减少从而应付大量连接情况
性能能够得到改善
实际上使用 select 系统调用 Apache 1.x 相比使用 epoll kqueue 事件监视
180
----------------------- Page 191-----------------------
4.2 C10K
问题
API
Apache 2.0 ,一点性能提升 20% ~30%。
int epfd; ①
首先创建用于epollfd,MAX_EVENTS监视fd数量
if ((epfd = epoll_create(MAX_EVENTS)) < 0) { epoll
fd创建失败
exit(1);
}
struct epoll_event event; ②
将要监视fd添加epoll,根据监视数量进行循环
int sock;
memset(&event, 0, sizeof(event));
初始化epoll_event结构
ev.events = EPOLLIN;
读取进行监视
ev.data.fd = sock;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, sock, &event) < 0) {
socket添加
epoll。fd
添加失败
exit(1);
}
int n, i; ③
通过epoll_wait进行监视
struct epoll_event events[MAX_EVENTS];
while (1) {
/ epoll_wait
参数*
第一:epollfd
第二:epoll_event结构数组
:epoll_event数组大小
:timeout时间毫秒
超时时间负数表示永远等待 /*
n = epoll_wait(epfd, events, MAX_EVENTS, -1);
if (n < 0) {
监视失败
exit(1);
}
for (i = 0; i < n; i++) {
fd处理
do_something_on_event(events[i])
}
}
close(epfd);
一般close关闭epollfd
3 epoll_create 3 示例程序
使用 libev 框架
即便我们知道 epoll kqueue 更加先进它们只能 Linux BSD 特定平台
使用一点十分苦恼因为 UNIX 平台好处就是稍稍用心一点可以比较
容易具备跨平台兼容性程序
181
----------------------- Page 192-----------------------
4 云计算时代编程
于是一些框架便出现它们可以平台相关部分隐藏起来实现文件描述监视
这些框架之中我们大家介绍一下 libev EventMachine 。
libev
提供高性能事件循环
1 /
首先包含<ev.h> /* *
Debian libev- 2 # include <ev.h>
dev
。libev loop 结构 3
4 /
其他文件 /* *
设定回调函数发生事件可读 5 # include <stdio.h>
6 # include <sys/types.h>
/
或者经过一定时间回调 7 # include <sys/socket.h>
函数调用 4 展示 libev 8
9 ev_io srvsock_watcher;
用法由于代码注释, 10 ev_timer timeout_watcher;
因此大家应该 libev 用法 11
12 /
读取socket回调函数 /* *
大致理解。 13 static void
14 sock_cb(struct ev_loop *loop, ev_io
程序基本上就是用于监视 *w, int revents)
15 {
watcher 进行初始化然后添加 16 / 读取socket处理 /* *
17 /
省略do_socket_read实现部分 /* *
循环结构 66 ~72 ),最后 18 / 到达EOF返回EOF /
* *
调用事件循环 75 可以。 19 if (do_socket_read(w->fd) == EOF) {
20 ev_io_stop(w); /
停止监视 /* *
接下来每次发生事件自动 21 close(w->fd); / 关闭fd /* *
22 free(w); /
释放ev_io /* *
watcher 设定回调函数 12 ~
23 }
52
)。服务器回调函数 24 }
25
27 ~42 接受 26 / 服务器socket回调函数 /
* *
客户端连接添加事件循环 27 static void
28 sv_cb(struct ev_loop *loop, ev_io w, *
。 int re
vents)
这个例子涉及输出输出 29 {
30 struct sockaddr_storage buf;
以及超时事件 libev 能够监视 31 ev_io *sock_watcher;
32 int s;
1 所有这些种类事件。 33
34 /
接受客户端socket连接 /* *
1 libev监视事件一览 35 s = accept(w->fd, &buf,
sizeof(buf));
     
36 if (s < 0) return;
ev_io
输入输出 37
ev_timer
相对时间(n ) 38 / 开始监视客户端socket /* *
39 sock_watcher = malloc(sizeof(ev_
ev_periodic
绝对时间 io));
ev_stat
文件属性变化 40 ev _io_init(sock_watcher, sock_cb, s,
ev_signal
信号 EV_READ);
ev_child
进程变化 4 libev 用法
ev_idle
闲置
182
----------------------- Page 193-----------------------
4.2 C10K
问题
libev
使
41 ev_io_start(loop, sock_
epoll 、kqueue、/dev/poll
事件监视 API 。 watcher);
42 }
这些 API 不可使用 select 系统 43
使用 libev ,可以监视并发连接 44 / 超时回调函数 /* *
45 /
事件循环60调用 /* *
无需担心移植性。 46 static void
47 timeout_cb(struct ev_loop loop, *
使用 libev 这样 事件驱动 ev_timer w, int revents)*
48 {
须要注意回调函数不能发生阻塞 事件 49 puts("timeout");
50 / *
结束所有事件循环 /*
循环线程工作因此回调函数 51 ev_unloop(loop, EVUNLOOP_ALL);
执行过程无法别的事件进行处理。 52 }
53
不仅 libev ,所有事件驱动架构程序, 54 int
55 m ain(void)
必须尽快结束回调处理如果工作 56 {
需要花费时间可以发给其他 57 / 获取事件循环结构 /* *
58 / *
一般default可以 /*
进程 / 线程完成。 59 struct ev_loop loop = ev_
*
default_loop(0);
60
61 / *
服务器socket获取处理 /*
使用 EventMachine 62 / * 篇幅省略get_server_
socket
实现部分 /*
63 / socket, bind,
执行socket、*
刚刚我们底层编程工作 bind、listen*/
64 int s = get_server_socket();
C 语言不过仔细的话, 65
正如开始那样,C10K 问题本质 66 / 开始监视服务器socket /* *
67 ev_io_init(&srvsock_watcher,
其实明明硬件性能足够来自客户 sv_cb, s, EV
_READ);
端的并发连接过多导致处理产生破绽”。既然 68 ev_io_start(loop, &srvsock_
我们完全可以那么在意 CPU 性能那是 watcher);
69
不是 Ruby 能够应对 C10K 问题? 70 / 设置超时时间(60) /
* *
71 ev_timer_init(&timeout_
答案肯定实际上 Ruby 开发 watcher, timeout_cb, 60.0, 0.0);
72 ev_timer_start(loop, &timeout_
应付大量并发连接程序并不支持 watcher);
73
框架已经有了 我们介绍一种
74 / *
事件循环 /*
Ruby
应对 C10K 问题事件驱动框架—— 75 ev_loop(loop, 0);
76
EventMachine 。
Ruby 77 / unloop调用结束事件循环 /
* *
RubyGems
可以轻松完成 EventMachine 78 return 0;
79 }
安装
4 libev 用法
$ sudo gem install eventmachine
表示换行
183
----------------------- Page 194-----------------------
4 云计算时代编程
EventMachine
1 require 'eventmachine'
Echo (回声服务器 2
3 module EchoServer
功能就是 socket 数据 4 def post_init
原本返回程序 5 。 5 puts "-- someone connected to the echo
server!"
4 运用 libev 编写程序足足 6 end
7
80 还是省略本质
8 def receive_data data
处理部分情况 5 程序 9 send_data data
~
10 close_connection if data = /quit/i
完整需要 20 由此大家 11 end
可以受到 Ruby 表达 12
13 def unbind
。 14 puts "-- someone disconnected from the echo
server!"
EventMachine 回调 15 end
16 end
Ruby 模块形式进行定义。 17
18 EventMachine::run {
5 例子,EchoServer 模块 19 EventMachine::start_server "127.0.0.1", 8081,
扮演这个角色这个模块 EchoServer
20  }
几个方法从而实现回调
5 运用 EventMachine 编写 Echo 服务器
就是一种 Template Method 设计
实现回调方法 2
2 EventMachine回调方法
   调用条件   
post_init socket
连接 初始化连接
receive_data(data)
数据接收 读取数据
unbind
连接终止 终止处理
connection_completed
连接完成时 初始化客户端连接
ssl_handshake_completed SSL
连接 SSL
ssl_verify_peer SSL
连接 SSL 节点验证
proxy_target_unbound proxy
关闭 转发目标
同样事件驱动框架 libev EventMachine 功能不同
libev
目的只是提供基本事件监视功能连接内存管理方面需要
自己操心同时能够支持定时器信号进程状态变化各种事件。libev 用于 C
语言虽然程序可能变得繁琐拥有可以应付各种状况灵活性
另一方面,EventMachine 提供 并发网络连接处理方面丰富功能 5 程序
可以看出由于连接数据读取提供相应支持因此网络编程方面
184
----------------------- Page 195-----------------------
4.2 C10K
问题
节约大量代码相对支持事件种类只有输入输出定时器
C ,libev Ruby
EventMachine
支持包括服务器客户端的网络连接输入输出甚至 SSL 加密也许
反映编程语言性格之间差异
其实关于 libev EventMachine 是否真的能够处理大量并发连接最好是个性测试
简陋环境恐怕无法尝试客户端的连接不可能为了这个实验准备
笔记本电脑而且进行扩展实验还是需要准备专门网络环境
不过话说回来,libev EventMachine 已经各种服务拥有一些应用实例应该不会存在
极端性能问题① 。
小结
libev 、EventMachine 事件驱动框架如何尽量缩短回调执行时间非常重要
因为回调如果发生输入输出等待阻塞),大量并发连接情况致命于是
输出如何避免阻塞阻塞 I/O )显得愈发重要
有人 EventMachine 性能功能表示不满于是开发异步 I/O ——Celluloid.io 。(
185
----------------------- Page 196-----------------------
4 云计算时代编程
4.3 HashFold
4-1 我们介绍大量信息创造记录引发信息爆炸”,以及应付信息爆炸
处理分布计算机进行方法对于运用计算机构成高度分布式处理环境
编程模型我们介绍美国 Google 公司提出 MapReduce。下面我们介绍 HashFold
一种变体,Steve Krenzel 网站(http://stevekrenzel.com/improving-mapreduce-with-
hashfold )
介绍
MapReduce
通过分解提取数据流 Map 函数计算数据 Reduce 函数处理
进行分割从而实现大量数据高效分布式处理
相对,HashFold 功能是以列表方式接收 Map 数据然后通过 Fold 过程
列表元素重复这种模型 MapReduce 一些没有细化部分 Map 数据
排序进行 Reduce 通过列表数据结构性质清晰描述因此个人喜欢
HashFold 。
不过虽然 HashFold 表示支持恐怕成为主流还是困难
即便无法成为主流对于大规模数据处理中分处理实现,HashFold 简洁结构应该
可以成为不错实例
HashFold
Map
def map(document)
接收文档分解单词
key 、 document: document contents#
for word in document
value
然后,Fold 过程接收 key=单词计数=1#
value ,
返回 value。 1 yield word, 1
end
就是运用 HashFold end
计数程序
def fold(count1, count2)
单词进行统计
count1, count2: two counts for a word#
单词计数 MapReduce 主要 return count1 + count2
应用实例这个说法已经老生 end
每次提到 MapReduce 的话 1  HashFold 编写单词计数程序概念
当成例题 HashFold 单词计数最佳计算模型当然可以用来进行
其他计算
186
----------------------- Page 197-----------------------
4.3 HashFold
下面我们按照 1 概念实现简单 HashFold 因为如果实际实现一下的话
我们无法判断这种模型是否具有广泛适应性
为了进行设计我们需要思考满足 HashFold 性质条件于是便得出以下结论
首先由于 Ruby 无法通过网络发送过程因此 HashFold 主体函数过程),
应该对象如果对象的话只要通过某些手段事先共享定义我们可以 Ruby
Marshal 功能通过网络传输对象

class WordCount < HashFold
HashFold def map(document) 分割单词
for word in document
,HashFold
yield word, 1
拥有功能可以 end
end
继承下来从而可以使用
Template Method
模式 def fold(count1, count2) 重复单词进行合并
计算
单独 Map Fold return count1 + count2
end
2 )
end
h = WordCount.new.start(documents)
得到结果Hash(单词=>
单词出现
我们制作 2 HashFold API(概念
上述设计 HashFold
HashFold
实现(Level 1 )
我们已经完成 API 设计现在我们实际进行 HashFold 实现首先我们
考虑分布式环境而是初级版本开始
实现单纯别的 HashFold 容易只要接收输入数据执行 Map
过程如果出现重复通过 Fold 过程解决实际程序 3
考虑分布式环境情况,HashFold 实现其实相当容易反映 HashFold “
理解特性
不过实际运行一下知道是不是真的于是我们准备个例程序
4 就是 HashFold 准备例题程序可以看成 2 中的概念进行具体化结果
依照 MapReduce 传统方式单词进行计数程序今后我们 HashFold
187
----------------------- Page 198-----------------------
4 云计算时代编程
升级 API 不变因此单词计数程序需要进行改动
class HashFold
def start(inputs)
hash = {}
保存结果Hash
inputs.each do |input|
传递start输入数据调用map方法
self.map(input) do |k,v|
if hash.key?(k)
代码传递 keyvalue如果出现重复调用fold方法
hash[k] = self.fold(hash[k], v)
else
hash[k] = v
如果尚未存在存放Hash
end
end
end
hash
返回结果Hash
end
end
3 HashFold (Level 1)
class WordCount < HashFold
需要进行计数高频英文单词
STOP_WORDS = %w(a an and are as be for if in is it of or the to with)
输入
参数作为
文件
def map(document)
open(document) do |f|
文件执行操作
for line in f
所有标点符号视为分割换成空格
~
line.gsub!(/[! "$%&\'() +,-.\/:;<=>?@\[\\\]^_`{\|} ]/, " ") # *
一行内容
分割单词
for word in line.split
字母统一转换小写
word.downcase!
高频单词计数
next if STOP_WORDS.include?(word) key=
单词计数=1,传递代码
yield word.strip, 1
解决重复
end
end
end
end
def fold(count1, count2)
单词计数进行简单
return count1 + count2
命令行参数用于指定统计单词文件随后按照计数单词倒序排列
),输出在前20单词
end
end
WordCount.new.start(ARGV).sort_by{|x|x[1]}.reverse.take(20).each do |k,v|
print k,": ", v, "\n"
end
4 单词计数程序
188
----------------------- Page 199-----------------------
4.3 HashFold
3 4 程序结合起来完成简单 HashFold 程序我们暂且
程序存在“hfl.rb”这个文件
那么我们运行一下看看首先 Ruby
% ruby hf1.rb ChangeLog
仓库中的 “Ruby trunk”分支 ChangeLog ( ruby: 11960
rb: 11652
履历作为单词计数对象运行结果 5 c: 10231
。 org: 7591
lang: 5494
test: 4224
这个结果我们可以发现有趣 lib: 3804
比如,ruby 这个单词出现次数最多 ext: 3582
2008: 3172
当然出现次数最多名字提交 ditto: 2669
dev: 2382
nobuyoshi nakada (2313
),远远超出位于第二 nobu: 2334
( 1648 )。原来已经超越那么。 nakada: 2313
nobuyoshi: 2313
2007: 1820
除此之外我们看出提交发生最多 h: 1664
星期二如果查看一下 20 之后结果 matz: 1659
yukihiro: 1648
可以看出一周每天提交次数排名最多 matsumoto: 1648
星期二(1639 ),然后依次星期四( 1584 )、 tue: 1639
星期一(1503 )、星期五(1481 )、星期三( 1477 5 单词计数运行结果
)、星期六( 1234 星期日(1012 )。果然周末提交比较次数最多居然
星期二这个倒是没有想到
不过统计文件中的单词不是有意思我们多个文件作为计数对象
ChangeLog 以及其他位于 Ruby trunk 分支所有“.c”文件作为对象算了一下
统计文件数量 292 大小 6MB ,正好我们以来统计一下运行时间 6 )。这里
运行环境 Ruby 1.8.7 ,Patch Level 174 。
% time ruby hf1.rb ChangeLog ** */ .c
rb: 31202
0: 17155
1: 13784
ruby: 13205
(
)
ruby hf0.rb ChangeLog / .c 37.89s user 3.89s system 98% cpu** *
42.528 total
6 多个文件对象运行结果附带运行时间
shell “zsh”,可以通过“**/*.c”指定当前目录包括子目录所有 .c
文件这个功能非常方便甚至可以就是为了这个功能 zsh
189
----------------------- Page 200-----------------------
4 云计算时代编程
命令行前面加上 time 可以运行时间。time shell 内部命令因此 shell
输出格式不同大体上包含以下 3 信息
‰ user :
程序本身消耗时间
‰ system :
由于系统调用操作系统内核消耗时间
‰ total :
程序启动结束消耗时间由于系统运行其他进程因此这个时间
大于 user system
这样运算 42 不赖不过,6MB 数据即便进行什么优化
简单程序完成没有多大问题
Ruby 1.9 稿 trunk ,ruby1.9.2dev
(2009-08-08trunk 24443 )。
user 18.61 、system 0.14 、total 18.788 Ruby 1.8.7
42.528
相比速度到了两倍以上(2.26 )。看来 Ruby 1.9 搭载虚拟机“YARV (Yet
Another Ruby VM )”
性能不可小觑
从此之后我们基本上使用 1.9 版本进行测试主要因为平常常用就是 Ruby
1.9。
此外由于性能测试多次如果等待时间缩短的话可是大大提高稿
效率
运用必要性
如果程序运行速度恐怕意见相反无论编写程序运行速度
有人抱怨不够”。这种情况出现几乎必然太阳每天都会起来一样
问题不仅仅如此虽然 CPU 速度根据摩尔定律变得越来越马上就要遇到物理
定律极限,CPU 性能提升不会之前那样一帆风顺几年来,CPU 时钟频率提升
到了瓶颈,Intel 公司推出 Atom 这样频率低能 CPU 成功以及普通电脑
拥有多个 CPU 处理器普及这些逐步接近物理极限带来影响
此外还有信息爆炸问题我们面前处理数据变得非常巨大数据
消耗时间都会变得无法忽略 Google 公司处理 PB 级别数据光是数据
拷贝花费时间达到这个数量级
MapReduce
正是背景诞生技术,HashFold 需要考虑方面因素不断提升
性能
190
----------------------- Page 201-----------------------
4.3 HashFold
所幸联想 ThinkPad X61 安装 Intel Core2 Duo 这个双核 CPU ,没有理由
充分利用通过使用多个 CPU 进行同时处理并发编程处理性能提高提供

目前 Ruby 实现存在问题
然而充分利用角度目前 Ruby 实现存在问题作为并发编程
我们可以使用线程 Ruby 1.8 中的线程功能解释器级别实现因此只能同时
处理并不充分利用性能 Ruby 1.9 线程实现使用操作系统提供
pthread
本来应该可以利用 Ruby 1.9 为了保护解释器中非线程安全

加上称为 GIL (Giant Intepreter Lock ) 由于这个限制每次还是只能
执行线程看来 Ruby 1.9 利用困难
那么如果 Ruby 利用怎样一种方法采用没有 GIL 实现所幸
JVM 工作 JRuby 没有这个因此 JRuby 可以充分利用不过作为
Ruby
实现一点非要使用 JRuby 不可有点感觉
通过进程实现 HashFold (Level 2 )
如果线程不行的话进程好了。”不过仔细发现利用多个CPU 手段
操作系统不是原本已经提供就是进程如果启动多个进程操作系统自动进行
调配使得各个进程分配适当 CPU 资源
这样功能利用起来真是浪费首先我们简单程式实现即为
输入项目启动进程
输入启动进程 HashFold 实现 7 线程不同进程之间共享
因此为了返回结果需要进程通信这里我们使用 UNIX 编程经典父子
进程通信手段 pipe。
基本处理流程简单输入启动相应进程各个文件单词计数进程进行
计数结果 Hash 需要返回进程线程不同父子进程之间无法共享对象因此需要使
这个功能线程环境为了保护线程安全没有考虑线程同时并行运行代码允许
同时运行线程。(
191
----------------------- Page 202-----------------------
4 云计算时代编程
pipe Marshal 对象进行复制转发进程从子进程接收 Hash 收到 Hash
fold 方法进行合并最终得到单词计数结果
说到这里大家应该明白 7 程序 大致流程作为编程技巧希望大家记住关于
fork
pipe 用法它们使用进程程序几乎不可或缺技巧 Ruby ,fork 方法
可以附带代码进行调用代码可以进程运行运行代码末尾
自动结束
class HashFold
def hash_merge(hash,k,v)
调用fold,由于调用多次因此构建方法
if hash.key?(k)
hash[k] = self.fold(hash[k], v)
如果遇到重复调用fold方法
else
hash[k] = v
尚未存在存放Hash
end
end
def start(inputs)
hash = nil
inputs.map do |input|
传递start输入进行循环
p,c = IO.pipe
创建用于父子进程通信pipe
fork do
创建进程(fork),进程运行代码
p.close
关闭使用pipe
h = {}
保存结果Hash
self.map(input) do |k,v|
调用map方法由于完全复制进程内存空间因此可以看到
进程对象(input)
hash_merge(h,k,v)
存放数据解决重复
end
Marshal.dump(h,c)
结果返回进程使用Marshal
end
c.close
进程关闭使用pipe
p
进程pipe进行map
end.each do |f|
读取来自进程结果
h = Marshal.load(f)
if hash
结果Hash进行合并
h.each do |k,v|
hash_merge(hash, k, v)
end
else
hash = h
end
end
hash
end
end
#
单词计数部分共通
7 运用进程实现 HashFold
192
----------------------- Page 203-----------------------
4.3 HashFold
重要事情总要反复强调一下,fork 作用创建当前运行中的进程副本由于
副本因此现在可以引用对象进程可以直接引用但是正是由于只是
副本因此如果对象进行任何变更或者创建对象无法直接反映进程
一点共享内存空间线程不同
共享内存空间进程之间进行信息传递多种方法具有父子关系进程
pipe
恐怕最好方法
pipe
方法创建分别用来读取 IO (输入输出)。
r,w = IO.pipe
IO w 数据可以 r 读取出来正如刚才由于进程
进程副本进程创建 pipe ,进程pipe 进行的话可以从父进程
数据读取出来作为习惯我们应该使用 IO (这里进程用于
进程用于读取关闭避免资源浪费
这个程序从子进程传递结果需要创建一对 pipe ,如果需要双向通信创建pipe。
我们运行一下看看 7 HashFold 4 单词计数程序组合起来保存“hf2.
rb”,
运行这个程序 1.9 环境运行结果 user 0.66 、system 0.08 、total 11.494
并行运行时间 18.788 相比速度原来 1.63 考虑并行处理产生进程创建
开销以及 Marshal 通信开销,63% 改善算是可以
之所以 user system 时间非常因为实际单词计数处理几乎进程进行
因此没有进去顺便 1.8.7 运行时间 25.528 1.9 2.25
然而仔细看一看的话这个程序还是有一些问题这个程序输入文件
启动进程这样瞬间产生大量进程我们 292 文件单词进行计数
创建 293 文件数量 + 管理进程进程大量进程意味着巨大内存开销如果
统计对象文件数量继续增加因为进程数量引发问题
抖动
进程数量过多产生抖动现象
随着大量进程产生消耗大量内存空间最近操作系统申请分配内存
数量超过实际物理内存容量现在使用进程内存数据暂时存放磁盘
193
----------------------- Page 204-----------------------
4 云计算时代编程
内存空间变得这种技术称为虚拟内存
然而磁盘访问速度实际内存相比百万进程数量多时几乎所有
时间消耗磁盘访问实际处理陷于停滞就是抖动
其实 Ruby 需要代码可以产生大量进程从而故意引发抖动不过这里
还是介绍具体代码
当然操作系统方面考虑到了一点为了尽量避免发生抖动进行一些优化
复制(Copy-on-Write )技术就是创建进程对于所有内存空间并非开始
创建副本而是进行共享只有实际发生数据改写进行复制通过技术
可以避免对内空间浪费
Linux 还有称为 OOM Killer (Out of Memory Killer )功能发生抖动
选择适当进程强制结束从而抖动做出应对当然操作系统不可能人类意图
角度判断哪个进程重要因此 OOM Killer 有时候杀掉一些重要进程对于这个
功能评价毁誉参半
运用进程 HashFold (Level 3 )
大量产生进程带来问题我们已经了解那么我们可以每次创建进程然后舍弃
而是重复利用已经创建进程线程进程创建时候就伴随着一定开销因此这样
创建重复利用技术非常普遍这种重复利用技术称为(pooling )(8 )。
class HashFold
class Pool
用于进程
def initialize(hf, n)
初始化指定HashFold对象以及进程中的进程数量
pool = n.times.map{
创建n进程
c0,p0 = IO.pipe
通信管道从父进程进程输入
p1,c1 = IO.pipe
通信管道从子进程进程输出
fork do
创建进程
p0.close
关闭使用pipe
p1.close
loop do
重复利用执行循环
input = Marshal.load(c0) rescue exit
Marshal等待输入输入失败exit
hash = {}
保存结果Hash
hf.map(input) do |k,v|
调用HashFold对象nap方法
hf.hash_merge(hash,k,v)
数据保存解决重复
end
8 运用进程 HashFold
194
----------------------- Page 205-----------------------
4.3 HashFold
Marshal.dump(hash,c1)
结果返回进程
end
end
c0.close
进程关闭使用pipe
c1.close
[p0,p1]
输入输出pipe进行map
}
@inputs = pool.map{|i,o| i}
进程IO
@outputs = pool.map{|i,o| o}
进程读出IO
@ichann = @inputs.dup
可以进程 IO
@queue = []
队列
@results = []
读出队列
end
def flush
队列中的数据尽量
loop do
if @ichann.empty?
o, @ichann, e = IO.select([], @inputs, [])
使用select寻找 IO (a)
break if @ichann.empty?
如果没有 IO放弃
end
break if @queue.empty?
如果存在数据跳出循环
Marshal.dump(@queue.pop, @ichann.pop)
执行
end
end
private :flush
用作内部实现方法因此声明private
def push(obj)
Pool数据方法
@queue.push obj
flush
end
def fill
读出队列尽量读出数据
t = @results.size == 0 ? nil: 0 result
列为select阻塞
检查(timeout=0)
ochann, i, e = IO.select(@outputs, [], [], t)
获取等待读出 IO (b)
return if ochann == nil
发生超时时候
ochann.each do
c = ochann.pop
begin
@results.push Marshal.load(c)
rescue => e
c.close
@outputs.delete(c)
end
end
end
private :fill
用于内部实现方法因此声明private
def result
fill
Pool获取数据方法
8 运用进程 HashFold(
195
----------------------- Page 206-----------------------
4 云计算时代编程
@results.pop
end
end
def initialize(n=2)
@pool = Pool.new(self,n) HashFold
初始化参数构成进程
end
创建进程
def hash_merge(hash,k,v)
if hash.key?(k) Hash
合并
hash[k] = self.fold(hash[k], v)
else
hash[k] = v
end
end
def start(inputs)
inputs.each do |input| HashFold
计算开始
@pool.push(input)
输入传递 Pool
end
hash = {}
inputs.each do |input|
@pool.result.each do |k,v|
获取结果Hash
hash_merge(hash, k,v)
结果Hash进行合并
end
end
hash
end
end
8 运用进程 HashFold(
7 程序相比由于增加重复利用代码因此程序变得复杂不过想象
这个程序行为并不
7 程序相比具体区别在于并非输入生成进程而是实现启动一定数量
进程这些进程传递输入从中获取输出如此反复因此 7 程序需要
pipe ,程序需要分别用于输入输出pipe。
此外并发编程还有一点重要就是不要发生阻塞如果试图没有准备
数据 pipe 读取数据的话数据传递过来之前程序停止响应这种情况称为阻塞
如果是非并行程序数据准备之前发生阻塞正常不过并行程序
阻塞期间其他进程输入停滞结果完成处理需要时间增加
因此我们这里 select 避免阻塞发生。select 参数 IO 排列数组
返回数据准备 IO 数组。select 可以监视读取异常处理 3 数据我们
196
----------------------- Page 207-----------------------
4.3 HashFold
读取各自分别调用 select。
8 (a) 位于进程检查我们使用 select。select 参数监视
IO
数组这里我们需要检查只是因此 2 参数指定 IO 数组 1、
3
参数指定数组
8 (b) 我们进程读出结果进行检查。select 默认情况存在可读
IO 发生阻塞读出队列已经有的数据我们希望发生阻塞因此我们指定
4 参数也就是超时时间。select 4 参数指定整数等待时间不会超过这个
程序队列我们指定 0 ,也就是立即返回意思
避免发生阻塞除了 select 之外还有其他手段比如使用其他线程不过一般来说
通过 fork 创建进程线程推荐程序同时使用理由,pthread fork 组合
实际调用系统调用非常有限因此不同台上保证能够正常工作
出于这个原因同时使用 fork 线程程序可能导致 Ruby 解释器出现不可预料行为
例如报告 Linux 可以工作 FreeBSD 不行导致十分棘手 bug 。
那么我们 8 HashFold 测试一下实际运行速度之前其他程序一样 1.9
相同条件运行结果 user 0.72 ,system 0.06 ,total 10.604 由于存在生成大量
进程带来开销性能有了稍许提升此外抖动抵抗力应该提高顺便一句
1.8.7
运行时间 25.854
小结
对于我们这些古董程序员,fork、pipe、select 已经熟悉不过进程编程
API
这些 API 甚至可以最新架构上面真是感到无比爽快
不过目前一般PC ,虽说对于电脑也就是双核或者稍微
一些服务器可以达到 8 电脑拥有 CPU 核心(many-core )环境
尚未成为现实。HashFold 计算模型本来目的为了应对信息爆炸目前这种程度
CPU
核心数量无法应对信息爆炸别的数据处理
看来今后我们必须考虑计算机构成分布式环境
197
----------------------- Page 208-----------------------
4 云计算时代编程
4.4
进程通信
有限时间处理大量信息基本手段就是分割统治”。也就是说关键如何
大量数据进行并行处理并行处理充分利用电脑具备多个 CPU )
环境计算机进行处理显得非常重要
进程线程
并行处理单位大体上 1 处理单位同时运行特征
分为进程线程 1 )。 处理单位 内存空间共享 利用
线程 实现不同不同

进程
进程相互独立
方法无法看到改变其他进程内容状态 Linux UNIX 操作系统
无法中途保存状态转移另一计算机即便存在这种操作成为可能操作系统
只是停留研究阶段而已目前没有民用迹象
另一方面多个线程可以同一进程运行线程可以相互合作所属同一
线程 内存空间共享因此多个线程可以访问相同数据优点
同时缺点
优点 因为可以避免数据传输带来开销进程之间内存无法共享
因此进程通信需要数据进行拷贝线程之间进行数据共享需要进行数据传输
这种方式缺点就是由于多个线程访问相同数据因此容易产生冲突例如引用
更新一半数据或者相同数据同时进行更新导致数据损坏线程编程由于
操作时机导致棘手 bug 肯定遇到
虽然灵活使用线程重要总归线程使用范围计算机大规模
计算机无法处理我们主要介绍一下计算机环境中的进程
通信
198
----------------------- Page 209-----------------------
4.4 
进程通信
同一计算机进程通信
首先我们同一计算机进程通信正如我们 4-3 HashFold 实现
同一计算机充分利用多个进程可以带来一定好处尤其是现在 Ruby 实现由于
技术障碍使得线程利用变得困难(JRuby 除外),因此进程活用变得
重要
Linux UNIX 操作系统同一计算机进行进程通信手段以下
‰
管道(pipe )
‰
消息(message )
‰
信号(semaphore )
‰
共享内存
‰ TCP

‰ UDP

‰ UNIX

我们依次解释一下管道通过 pipe 系统调用创建一对文件描述进行通信
方式所谓文件描述就是表示输入输出对象一种识别 Ruby 对应 IO 对象
数据 pipe 入时可以另一端的 pipe 读出事先管道准备然后“fork”系统
调用创建进程这样可以实现进程通信
消息信号共享内存 UNIX System V(5) 版本加入进程通信 API 。其中
用于数据通信信号用于互斥共享内存用于进程共享内存状态它们结合起来
称为 sysvipc。
不过上述这些手段不是流行例如管道优点在于父子关系进程之间可以
通信但是不再使用必须销毁否则一直占用操作系统资源说实话不是
API ,关于使用信息于是更加不想真是恶性循环
(socket )进程通信一种通道原本4.2BSD 包含功能
除了 UNIX 操作系统之外包括 Windows 在内各种其他操作系统提供这样功能
根据通信对象指定方法以及通信性质可以分为不同种类 主要使用
TCP 、UDP UNIX 它们性质 2
使用进行通信需要事先设定连接目标通过双方相互连接创建
通道这个连接目标指定方法种类使用最多 TCP UDP
199
----------------------- Page 210-----------------------
4 云计算时代编程
通过主机地址主机名或者 IP 地址口号( 1 65535 之间整数组合
指定
2 分类特征
种类 连接目标指定方法 数据分隔 可靠性
TCP
主机地址 + 口号 保存
UDP
主机地址 + 口号 保存
UNIX
路径 保存
位于网络中的计算机拥有称为 IP 地址识别码(IPv4 4 字节序列

IPv6
16 字节序列)。例如 IPv4 自己正在使用电脑对应IP 地址“ 127.0.0.1” 。
开始通信通过指定对方计算机 IP 地址相当于决定计算机进行通信
IP
地址数字非常难记因此计算机还有属于自己主机名”。这里
讲述细节不过简单通过 DNS (Domain Name System ,域名系统机制
可以主机名获得 IP 地址
另一方面,UNIX 使用文件一样路径指定连接目标服务器
监听 UNIX 需要指定路径结果就是 UNIX 创建这个
指定路径
路径作为连接目标意味着 UNIX 只能用于同一计算机进程通信
不过,UNIX 具备一些特殊功能不仅可以传输一般节流可以传输
描述
TCP
称为(stream socket ),数据只能作为单纯节流对待
因此无法保存每次数据长度信息
相对,UDP UNIX 数据作为数据传输单位
发送因此可以获取每次数据长度不过数据数据根据操作
设定长度进行分割
对于 UDP 有一点需要注意就是基于 UDP 通信具备可靠性所谓
没有可靠性就是说通信过程可能发生数据到达顺序颠倒情况可能发生
数据传输过程丢失情况
① IPv6
地址表示“::1”,关于 IPv6 细节不再赘述。(
200
----------------------- Page 211-----------------------
4.4 
进程通信
TCP/IP
协议
利用网络进行通信协议(protocol )迄今为止已经出现多种其中一些因为各种
已经淘汰现在依然幸存下来就是一种叫做 TCP/IP 协议。TCP 就是 TCP
协议进行通信意思
TCP
Transmission Control Protocal (传输控制协议缩写。TCP 负责错误恢复
发送流量控制行为高层协议一种低层 IP 协议 Internet Protocol )

基础之上实现
UDP
User Datagram Protocol (用户数据协议缩写。UDP 实际上 IP 基础
穿马甲 TCP 相比以下这些不同
1.
保存通信数据长度
TCP 发送数据作为节流处理虽然实际通信过程数据流
一定大小数据 TCP 这些连接在一起无法按照单位查看数据
相对通过 UDP 发送数据直接数据单位进行发送作为发送单位数据
长度一直保存数据接收不过如果长度超过操作系统规定长度一般
9KB
左右分割因此无法保证总是获取原始数据长度
2.
没有纠错机制
发送数据经过网络到达接收过程可能发生一些状况比如数据顺序
发生调换情况甚至发生整个数据丢失 TCP 数据都会赋予
编号如果顺序调换或者本来应该收到没有收到接收通知发送编号
没有收到”,请求发送重新发送这样可以保证数据不会发生遗漏
此外可以网络繁忙时候一次发送数据大小数量进行调节避免网络
发生阻塞
相对,UDP 没有这些机制顺序调换”、“发送数据收到这样情况
自己应付
网络通信协议分为 5 分别对应 OSI 通信模型中的 3 ~7 ),其中 TCP UDP 属于
”(OSI 6 ),IP 属于一级网络层”(OSI 5 ),常见 HTTP 、FTP 属于最高
”(OSI 7 )。
201
----------------------- Page 212-----------------------
4 云计算时代编程
3.
需要连接
TCP 通信对象固定因此如果多个对象进行通信需要对象
使用不同
相对,UDP 使用 sendto 系统用来指定发送目标因此每次发送数据可以
送给不同目标接收数据可以使用 recvfrom 系统调用一并获取发送信息
虽然 UDP 需要进行连接需要情况可以进行连接固定通信对象
4.
高速
由于 TCP 可以进行复杂控制因此使用起来比较方便但是由于需要处理工作
其实便折扣
UDP
由于处理工作非常因而能够发挥 IP 协议本身性能一些实时大于可靠性
网络应用程序出于性能考虑选择 UDP 。
例如音频传输即便数据发生丢失只不过造成一些音质损失例如产生
杂音而已 之下维持延迟显得更加重要这样案例比较适合采用
UDP
协议进行通信
C 语言进行编程
使用已经有了系统调用构建 C 语言 API 。通过 C 语言可以访问
相关系统调用 3 。TCP 使用方法步骤以及无连接 UDP 步骤 1
3 相关系统调用
系统调用   
accept (fd, addr, len )
接受连接返回
bind (fd, addr, len )
服务器命名
connect (fd, addr, len )
连接
getsockopt (fd,level,optname,optval,optlen )
获取选项
listen (fd, n )
设置连接队列固定用法
recv (fd, data, len, flags )
接收数据指定 flags )
recvfrom (fd, data, len, flags, addr, alen )
包含发送信息数据接收
send (fd, data, len, flags )
发送数据指定 flags )
sendto (fd, data, len, flags, addr, alen )
指定接收数据发送
setsockopt (fd,level,optname,optval,optlen )
设置选项
socket (domain, type, protocol )
创建
202
----------------------- Page 213-----------------------
4.4 
进程通信
2 使用相关 TCP使用方法 UDP使用方法
服务器
调用进行通信客户端 创建(socket)
创建(socket)
这个程序访问(localhost )
声明等待连接状态(bind) 指定目标输出(sendto)
13 端口来自端口 接收发送信息
直接输出标准输出设备接受连接(accept) 数据(recvfrom)
获得客户端连接
13
端口返回当前时间 输入输出(read, write, recv, send)
“daytime”
服务端口号码返回 客户端
当前时间字符串最近 创建(socket)
操作系统倾向于关闭所有不必要 连接(connect)
服务因此 daytime 服务可能不可 输入输出(read, write, recv, send) 括号系统调用
如果电脑 daytime 服务
1  连接 TCP 无连接 UDP 使用方法
正常工作的话运行这个程序
类似下面这样字符串
Sat Oct 10 15:26:28 2009
# include <stdio.h>
# include <string.h>
# include <sys/types.h>
# include <sys/socket.h>
# include <netdb.h>
int main()
{
int sock;
struct sockaddr_in addr;
struct hostent host;*
char buf[128];
int n;

sock = socket(PF_INET, SOCK_STREAM, 0); socket
系统调用
addr.sin_family = AF_INET;
host = gethostbyname("localhost");
memcpy(&addr.sin_addr, host->h_addr, sizeof(addr.sin_addr));
addr.sin_port = htons(13); / daytime service /* *
connect(sock, (struct sockaddr )&addr, sizeof(addr));*
n = read(sock, buf, sizeof(buf));
buf[n] = '\0';
fputs(buf, stdout);
}
2  C 语言编写网络客户端
C 语言编写程序仅仅打开读取数据这么简单操作需要十分繁琐
代码
203
----------------------- Page 214-----------------------
4 云计算时代编程
我们看一看程序内容首先通过 15 4 socket系统调用参数
socket 系统调用创建其中参数意思使 协议类型   
基于 IP 协议连接(TCP )(4 )。3 参数“0” PF_INET IPv4 协议
表示使用指定通信普通协议一般情况 PF_INET6 IPv6 协议
PF_APPLETALK ApleTalk
协议
0 可以
PF_IPX IPX
协议
16 ~ 19 用于指定连接目标。sockaddr_in 通信类型
存放连接目标地址类型类别)、主机地址 SOCK_STREAM 节流
SOCK_DGRAM
数据
口号
需要注意 19 指定口号 htons() 。htons() 函数功能 16 整数转换
字节(network byte order ),字节发送顺序由于连接目标指定全部需要
网络字节进行如果忘记这个函数进行转换的话无法正确连接
服务器程序更加复杂因此这里不再赘述不过大家应该 C 语言处理网络连接
大概了解
Ruby 进行编程
系统调用基础 C 语言编程相当麻烦那么,Ruby 怎么样 3 2
C
语言程序拥有相同功能 Ruby 程序
require 'socket'
print TCPSocket.open("localhost", "daytime").read
3 Ruby 编写网络客户端
值得注意除了引用声明“require”一行之外实质上需要一行代码完成
连接通信 C 语言相比,Ruby 优势相当明显
进行网络编程 Ruby 擅长领域之一原因如下
1.
瓶颈
程序开发对于是否采用 Ruby 这样脚本语言犹豫不决理由之一就是运行性能
比较简单工作如果由于解释器实现方式导致性能下降影响相当
简单循环测试程序性能那么 Ruby 程序速度可能只有 C 语言程序十分之一
百分之一一点大家不禁担心这么到底不能
204
----------------------- Page 215-----------------------
4.4 
进程通信
不过程序运行时间其实大部分消耗 C 语言编写内部对于拥有一定规模
实用程序差距没有那么
进一步对于网络通信为主程序瓶颈几乎在于通信部分本地访
数据相比网络通信速度非常既然瓶颈在于通信本身那么其他部分即便运行
整体性能关系不大
2.
高级 API
C
语言可以使用 API 包括结构多个系统调用非常复杂
2 C 语言程序为了指定连接目标必须初始化 sockaddr_in 结构非常麻烦
相对 Ruby 由于 TCPSocket 提供比较高级 API ,因此程序可以变得更加简洁易懂
如果 C 语言一样使用全部功能通过支持直接访问系统调用 Socket 可以

Ruby
功能
那么我们详细看看 Ruby 功能 Ruby 功能“socket”
提供使用 socket 功能需要 Ruby 程序通过下面方式加载这个
require 'socket'
socket
提供包括 BasicSocket、IPSocket、TCPSocket、
IO
TCPS erv er 、U DPSock et 、UN IX Socket 、UN IX Serv er

Socket (
4 )。客户端编程恐怕其中最多应该 BasicSocket
TCPSocket ,
服务器 TCPServer 。 IPSocket
TCPSocket
其中 Socket 调用操作系统接口所有
由于直接访问操作系统接口因此程序变得 TCPServer
UDPSocket
复杂。Ruby 属于 IO 因此可以
进行普通输入输出操作一点非常方便。 UNIXSocket
UNIXServer
BasicSocket
IO 同时其他所有 Socket
。BasicSocket 抽象并不创建实例 4 相关
BasicSocket
中的方法 5
IPSocket
BasicSocket TCPSocket 、UDPSocket 包含
205
----------------------- Page 216-----------------------
4 云计算时代编程
共通一些功能抽象。IPSocket 中的方法 6
5 BasicSocket方法
实例方法   
close_read
关闭读取
close_write
关闭
getpeername
连接目标信息
getsockname
自己信息
6  IPSocket方法
getsockopt (opt )
获取选项
recv (len [,flag ])
数据接收 实例方法   
send (str [,flag ])
数据发送 addr 自己信息
setsockopt (opt,val )
设置选项 peeraddr 连接目标信息
shutdown ([how ])
结束通信 recvfrom (len [,flag ]) 数据接收
TCPSocket
连接通信对方进行连接进行连续数据传输
TCPSocket
具体 直接创建实例)。创建实例需要使用 new 方法,new 方法
调用方式 new (host, port) ,可以完成创建连接操作
TCPServer
TCPSocket 服务器版本 7 TCPServer方法
这些可以大大简化服务器端的处理     
new 方法指定参数可以限定接受 new ([host, ]port ) 创建连接
来自第一参数指定主机连接 7 )。 实例方法
accept
接受连接
UDPSocket
UDP 提供支持 listen (n ) 设置连接队列
。UDP 无连接特征
可以保存每次数据长度。UDPSocket 8 UDPSocket方法
方法 8      
new ([socktype ])
创建
UNIXSocket
UNIX
实例方法
UNIX
一种用于同一计算机进程 bind (host,port ) 命名
通信手段通信目标指定采用文件 connect (host, port ) 连接
路径方式其他方面 TCPSocket 相同 send (data [,flags,host,
发送数据
需要连接进行输入输出。UNIXSocket port ])
中的方法 9
send_io
recv_io 方法 UNIX 功夫使用方法可以通过
UNIX
文件描述传递其他进程一般来说进程传递文件描述只能
206
----------------------- Page 217-----------------------
4.4 
进程通信
具有父子关系进程共享一种方式使 9 UNIXSocket方法
UNIX 可以父子关系进程      
实现文件描述传递。 new (path ) 创建
socketpair
创建
UNIXServer
UNIXSocket
实例方法
TCPServer 一样用于简化服务器
path
路径
其中补充方法 TCPServer 相同。 addr 自己信息
peeraddr
连接目标信息
最后介绍 Socket 底层
recvfrom (len [,flag ])
数据接收
接口。Socket 拥有方法对应 C 语言
send_io (io )
发送文件描述
API , 使 Socket
recv_io([klass,mode ])
接收文件描述
可以 C 语言一样进行同样细化程序
由于这样实在繁琐所以实际上
Socket
中的方法 10 相关各类
功能一览 11
10 Socket方法
     
new (domain,type,protocol )
创建
socketpair (domain,type,protocol )
创建
gethostname
获取主机名
gethostbyname (hostname )
获取主机信息
gethostbyaddr (addr, type )
获取主机信息
getservbyname (name[,proto] )
获取服务信息
getaddrinfo (host,service[,family,type,protocol] )
获取地址信息
getnameinfo (addr[,flags] )
获取地址信息
pack_sockaddr_in (host,port )
创建地址结构
unpack_sockaddr_in (addr )
解包地址结构
实例方法
accept
等待连接
bind (addr )
命名
connect (host, port )
连接
listen (n )
设置连接队列
recvfrom (len[,flag] )
数据接收
207
----------------------- Page 218-----------------------
4 云计算时代编程
11 相关
BasicSocket
所有抽象
IPSocket
执行 IP 通信抽象
TCPSocket
连接
TCPServer TCPSocket
服务器
UDPSocket
无连接数据
UNIXSocket
用于同一主机进程通信
UNIXServer UNIXSocket
服务器
Socket
使用 Socket 系统调用所有功能
Ruby 实现网络服务器
我们已经通过 C、Ruby 语言介绍 require 'socket'
编程例子下面我们看看服务器 s = TCPServer.new(12345)
loop {
端的设计刚才那个访问 daytime 服务程序 cl = s.accept
无法成功运行于是我们编写 cl.print Time.now.strftime("%c")
cl.close
daytime 服务器拥有相同功能服务器程序。 }
daytime root 使 5 Ruby 编写网络服务器
(1024
以内端口需要 root 权限 ),因此我们
连接端口设置 12345 (5 )。
这样完成网络服务器可能印象庞大其实出人意料简单归功
TCPServer 提供高级 API 。
运行这个程序然后另一终端窗口运行刚才客户端程序(C 语言 2 ,
Ruby
3 ),运行之前 daytime 部分换成“12345”。运行结果如果显示类似
下面这样时间表示成功
Mon Jun 12 18:52:38 2006
下面我们简单讲解一下 5 这个程序 2 我们创建 TCPServer ,参数
连接口号仅仅如此我们完成 TCP 服务建立
3 开始循环 4 对于 TCPServer 调用 accept 方法。accept 方法
来自客户端的连接如果连接请求返回客户端建立连接我们这里
赋值变量 cl。客户端 TCPSocket 对象 IO 因此可以
执行一般输入输出操作对象
208
----------------------- Page 219-----------------------
4.4 
进程通信
5 print 当前时间,daytime 服务处理这么处理完成客户端 close
然后调用 accept 等待下一个连接
5 程序请求逐一进行处理对于
require 'socket'
daytime
这样仅仅返回时间服务也许 s = TCPServer.new(12345)
loop {
如果更加复杂处理的话这样可就不行了。 Thread.start(s.accept) { |cl|
如果 Web 服务器完成处理之前无法接受 cl.print Time.now.strftime("%c")
cl.close
下一个请求处理性能下降无法容忍 }
}
地步这样情况使用线程进程进行
处理比较常见做法使用线程进行并行 6 线程实现并行处理程序
程序 6
正如大家所见 Ruby 进行网络编程非常容易认为提到 Ruby 必然
Web 编程或许不如只有网络编程才能发挥 Ruby 真正价值
小结
利用我们可以通过网络地球另一端的计算机进行通信不过所能
数据只是字节序列而已如果传输文本以外数据传输需要数据转换字节序列
这种转换一般称为序列(serialization )或者(marshaling )。分布式编程环境
由于产生大量数据传输因此序列通常成为左右整体性能重要因素
209
----------------------- Page 220-----------------------
4 云计算时代编程
4.5 Rack
Unicorn
Web
应用程序服务器主要 HTTP 服务器 Web 应用程序框架构成说起 HTTP 服务器
Apache
有名除此之外还有其他多种例如高性能新型轻量级服务器 nginx 、
Ruby 实现作为 Ruby 标准组件附带 WEBrick ,以及高速著称 Mongrel Thin
此外,Web 应用程序框架方面除了鼎鼎大名 Ruby on Rails 之外出现 Ramaze、
Sinatra
Rails”框架于是对于 Web 框架必须所有 HTTP 服务器以及
程序服务器提供支持这样组合方式多如牛毛
为了解决如此组合出现一种 Rack 东西 1 )。Rack Python WSGI ①
影响开发用于连接 HTTP 服务器框架。HTTP 服务器 Rack 发送请求
然后接受响应进行处理可以连接所有支持 Rack 框架同样框架需要
Rack 支持可以支持大多数 HTTP 服务器最近 Ruby 基础 Web 应用程序
包括 Rails 在内基本上已经支持 Rack
Rack
基本原理 Rack 对象发送 HTTP
客户端浏览器
请求环境作为参数调用 call 方法
互联网 返回方式接收请求组织 HTTP 请求。Rack
Hello World
程序 2
HTTP
服务器
Rack
class HelloApp
Web
应用程序框架 def call(env)
[200, {"Content-Type" => "text/plain"},
Web
应用程序 ["Hello, World"]]
end
Web
应用程序服务器 end
2 HelloWorld Rack 应用程序
1 Web 应用程序服务器架构
① WSGI :Web Server Gateway Interface (Web
服务器网关接口缩写 Python 使用一种 HTTP 服务器
Web
应用程序框架之间通用接口。(
210
----------------------- Page 221-----------------------
4.5 Rack
Unicorn
Rack
对象所需要素包括下面
‰
带有参数 call 方法。call 方法调用参数表示请求环境 Hash 。
‰ call
方法返回 3 元素数组 1 元素状态代码整数), 2 元素表示
Hash , 3 元素数据本体字符串数组)。
Rack 专用特殊数据结构需要
为了看看 Rack 应用程序实际如何工作
require 'rubygems'
我们 2 程序保存“hello.rb”这个文件另外 require 'rack'
require 'hello'
准备名为“hello.ru”配置文件 3 )。hello.ru
配置文件其实只是单纯 Ruby run HelloApp.new
而已准备 hello.ru 文件之后我们可以使用 Rack 3 配置文件 hello.ru
应用程序启动脚本“rackup”启动应用程序
$ rackup hello.ru
然后我们只要 Web 浏览器访问 http://localhost:9292/ ,显示 Hello World
我们默认配置口号“9292”,HTTP 服务器“WEBrick”,通过配置文件
可以修改这些配置
Rack
中间件
Rack
规则简单就是 HTTP 请求作为环境对象进行 call 调用然后接收响应因此
无论 HTTP 服务器还是框架可以容易提供支持
应用这样机制只要实际框架进行调用之前补充相应 call ,可以修改框架
前提所有支持 Rack Web 应用程序增加具备通用性功能这种方式称为“Rack
中间件”。
Rack
默认自带 Rack 中间件 1
中间件使用可以通过“.ru”文件中用“use”进行指定例如如果 Web 应用
添加显示详细日志产生异常声称生成错误页面以及显示错误状态页面功能可以 3
hello.ru 文件改写 4 这样功能添加需要一行代码可以完成可见表述
优秀
211
----------------------- Page 222-----------------------
4 云计算时代编程
1 Rack中间件
require 'rubygems'
       require 'rack'
require 'hello'
Rack::Auth::Basic BASIC
认证
Rack::Auth::Digest Digest
认证 use Rack::CommonLogger
Rack::Auth::OpenID OpenID
认证 use Rack::ShowExceptions
use Rack::ShowStatus
Rack::Reloader
Ruby 脚本更新重新加载
Rack::File
显示静态文件 run HelloApp.new
Rack::Cascade
组合 Web 应用找不到文件尝试下一个
4 hello.ru(使用中间件
Rack::Static
指定目录显示静态文件
Rack::Lint
检查应用是否符合 Rack 规范开发
Rack::ShowExceptions
应用产生异常生成错误页面
Rack::ShowStatus
生成状态 400、500 错误页面
Rack::CommonLogger
生成 Apache common log 格式日志
Rack::Recursive
异常进行跳转
Rack::Session::Cookie
cookie 进行会话管理
Rack::Session::Pool
会话 ID 进行会话管理
应用程序服务器问题
正如上面讲到只要使用 Rack ,HTTP 服务
客户端浏览器
Web 框架可以进行自由组合这样一来
可以根据情况选择合适组合 如果网站 互联网
流量达到一定规模常见做法 Apache
Apache(
反向代理
nginx
前端用作负载均衡实际应用程序
通过 Thin Mongrel 进行工作 5 )。
Thin Thin Thin Thin
其中,Apache (或者nginx )负责接收来自客户
端的请求然后请求顺序发给下属 Thin 服务 Web应用程序
从而充分利用 I/O 等待情况产生空闲时间
5 Web 应用程序架构
此外最近服务器大多安装 CPU ,这样
多个进程分担工作架构可以限度利用
性能
Thin
一种十分快速 HTTP 服务器大多数情况这样架构已经足够
情况这种架构发生下面这些问题
‰
响应缓慢
212
----------------------- Page 223-----------------------
4.5 Rack
Unicorn
‰
内存开销
‰
分配均衡
‰
重启缓慢
‰
部署缓慢
下面我们具体看看这些问题内容
1.
响应缓慢
由于应用程序 bug ,或者数据库瓶颈原因应用程序响应有时候变得缓慢
应用方面问题,HTTP 服务器没有责任这样问题导致超时成为引发
问题元凶
为了避免这样问题其他请求产生负面影响默认情况 Thin 停止 30
没有响应任务产生超时不幸知道是不是 Ruby 线程实现关系这个超时
机制偶尔失灵
2.
内存开销
有些情况负责驱动 Web 应用程序 Thin 服务器程序内存开销变得非常
内存开销增加往往由于数据库连接释放或者垃圾回收机制未能回收已经死亡对象
各种原因引发
无论如何服务器内存容量总归有限如果内存开销产生过多浪费降低
整体性能
响应缓慢一样内存开销问题可能引发其他问题内存不足导致处理负担增加
处理负担增加导致其他请求应变应变导致用户不断尝试刷新页面结果
变得更加糟糕一旦环节出现问题多米诺骨牌一样产生连锁反应这样
情况不算少见
3.
分配均衡
Apache nginx 作为反向代理① ,多个 Thin 服务器进行请求分配情况请求
前端服务器顺序发给下属 Thin 服务器这种形态上层服务器下层发号施令”,
因此称为模式(push model )。
反向代理(reverse proxy )代理服务器配置Web 服务器网络实现 Web 服务器缓存安全性
负载均衡功能技术可以配置这种技术服务器本身。(
213
----------------------- Page 224-----------------------
4 云计算时代编程
一般来说模式,HTTP 服务器请求送给下属服务器并不知道目标服务器
状态原则上,HTTP 服务器只是顺序依次请求发给下属服务器而已
然而请求转发目标服务器由于某些原因没有完成请求处理分配
这个忙碌服务器请求只能等待而且请求知道什么时候才能处理完毕只能
倒霉
4.
重启缓慢
上面情况一旦请求处理发生延迟负面影响迅速波及范围
由于某些原因导致处理消耗时间必须迅速服务器进行重启虽然 Thin 自带超时
机制对于内存开销以及基于 CPU 时间进行服务器状态监控需要通过 Monit、God
程序完成
这些程序监控服务器进程发现问题进程强制停止并重启动即便如此
服务依然不是简单监控程序注意请求处理延迟马上重启服务器进程
这时框架应用程序代码需要全部重新加载恢复可以进行请求处理状态至少需要
秒钟 时间时间如果哪个倒霉请求分配这个正在重启服务器进程
不得不进行时间等待
5.
部署缓慢
需要 Web 应用程序进行升级必须重启目前正在运行所有应用程序服务器
刚才讲到仅仅重启多个服务器进程中的殃及一些走运请求
重启所有服务器进程的话 哪怕 Web 应用整体仅仅停止响应 10 秒钟
访问量网站带来想象损失
一种说法指出对于网站开始访问显示网页之间等待时间一般用户平均可以
接受极限 4 由于这个时间数据传输时间浏览器渲染 HTML 时间总和因此
Web
应用程序用于处理请求时间尽量控制 3 以内
如果上述说法成立的话那么目前这种前端配置反向代理请求送给下属
架构虽然平常没有问题一旦发生问题负面影响容易迅速扩大的确
缺点
于是我们下面介绍就是面向 UNIX Rack HTTP 服务器——Unicorn 。Unicorn
是以解决上述问题目标开发高速 HTTP 服务器之所以面向 UNIX”
Unicorn 使用 UNIX 操作系统提供 fork 系统调用以及 UNIX 因此无法
Windows
工作
214
----------------------- Page 225-----------------------
4.5 Rack
Unicorn
Unicorn
架构
Unicorn
系统架构 6 使用
客户端浏览器
Apache ,
换成nginx 一样
互联网
Unicorn
5 采用Thin 架构区别在于
Apache
需要通过 UNIX 单一 Master Apache
通信
Unicorn-Master
采用 Thin 架构,Apache 负责负载均衡
下属服务器分配请求采用 Unicorn 架构
Slave Slave Slave Slave
Apache
需要称为 Master 进程进行通信即可
这种通信通过 UNIX 完成
Web
应用程序
一般通过主机名口号指定通信
6 Web 应用程序架构
对象 UNIX 通过路径指定服务
数据接收通过指定路径创建 UNIX 指定路径生成
特殊文件开始通信一方只要一般文件一样数据接收看来通过
进行通信一样
UNIX
具有一些方便特性:①客户端可以文件一样进行读写操作;②进程
之间具备父子关系可以进行通信不过缺点由于通信对象指定采用路径形式
因此只能用于同一主机进程通信
然而对于主机分布式环境通过 Unicorn 进行负载均衡需求这种情况
可以 TCP 代替 UNIX 虽然性能一定下降
Apache 发给 Unicorn-Master 请求发给 Master 通过 fork 系统调用启动
Slave ,
实际处理 Slave 完成然后,Master Slave 处理完成之后响应发给
Apache 。
Unicorn
解决方案
不过这样机制如何解决 Thin 遇到问题对于上面提到 5 问题我们
一来看一看
215
----------------------- Page 226-----------------------
4 云计算时代编程
1.
响应缓慢
Web
应用响应本质上还是应用自身问题因此无法保证一定能够避免对于服务
重要问题出现如何避免波及其他无关请求
简单模式转发请求时候并不分配请求服务器确认是否已经
成对上一个请求处理因此导致其他无关请求处理发生延迟
相对 Unicorn 完成处理 Slave 主动获取请求模式(pull model ),
原理不会发生请求卡死忙碌服务器进程中的情况
不过便是模式并非完全存在请求等待问题访问量超出 Slave
绝对处理能力由于没有空闲 Slave 能够 Master 索取请求于是请求便不得不
Master
进行等待
如果 由于某种原因导致 Slave 完全停止运行情况由于 Slave 整体
处理能力随之下降 Unicorn 对于这样情况采取措施发现 Slave
处理超出规定时间强制重启 Slave。
2.
内存开销
响应缓慢问题一样内存消耗影响其他请求因此发生问题
如何影响其他请求前提完成重启由于 Unicorn 可以快速完成 Slave 重启
因此可以比较轻松应对内存消耗问题理由我们稍后介绍
3.
分配不均
正如之前采用模式 Unicorn 不会发生请求分配不可服务器
进程问题 Unicorn 来自 Apache 请求通过 UNIX 传递单一 Unicorn-
Master ,
下属 Slave 自己请求处理完成之后 Master 索取下一个请求综上所述
Unicorn
不会发生分配不均问题
4.
重启缓慢
采用模式避免分配不均 Unicorn 优点另一优点就是能够快速重启
Unicorn
Slave 进行重启有利因素第一由于采用模式因此即便重启
Slave 无法工作不用担心任何请求分配 Slave 这样一来整体上来
不会发生处理停滞
216
----------------------- Page 227-----------------------
4.5 Rack
Unicorn
第二,Unicorn Slave 启动方法讲究使得实际重启花费时间因此得以大大
缩短
由于超时内存开销原因监控程序强制终止或者由于其他原因导致 Slave
停止,Master 注意 Slave 进程停止工作立即通过 fork 系统调用创建自身进程副本
使作为 Slave 进程启动由于 Unicorn-Master 开始启动已经载入包括框架
应用程序在内全部代码因此启动 Slave 需要调用 fork 系统调用 Slave 处理
切换进程可以
最近 UNIX 操作系统具备“Copy-on-Write”(复制)①功能从而
复制进程同时复制内存数据需要在内重新分配表示进程结构进程
分配内存空间可以调用 fork 系统调用进程实现共享随着进程执行实际
对内数据改写发生改写内存进行复制也就是说对内复制随着
执行过程循序渐进这样一来基本上可以避免内存复制延迟导致 Slave
开销
Thin
应用程序服务器重启过程更为复杂首先需要启动 Ruby 解释器然后
重新载入框架应用程序代码相比之下运用 Unicorn 系统中的 Slave 重启时间
这样一来可以毫不犹豫重启发生问题 Slave。此外由于恢复工作可以快速完成
避免系统整体响应产生延迟
5.
部署缓慢
Slave
重启速度意味着需要服务器整体重启部署工作可以快速完成此外
Unicorn 针对缩短部署时间进行其他一些优化 Unicorn-Master 进程收到 USR2
稍后详述,Master 进行操作步骤
(1)
启动 Master
Master
收到 USR2 信号启动 Ruby 解释器运行 Master 进程
(2)
重新加载
Master 载入框架应用程序代码这个过程 Thin 重启一样需要消耗一定时间
这个过程 Master 依然工作因此没有任何问题
① Copy-on-Write :
创建进程复制进程内存空间而是内存数据发生入时进行复制机制

217
----------------------- Page 228-----------------------
4 云计算时代编程
(3)
启动 Slave
Master 通过 fork 系统调用启动 Slave ,这样一来版本 Web 应用准备完毕
提供服务
Master 启动第一 Slave Slave 如果检测存在 Master ,进程发送“QUIT”
信号命令 Master 结束进程
然后 Master 开始运行版本应用程序此时 Master 依然存在服务切换
本身已经完成
(4)
Master 结束
收到 QUIT 信号 Master 停止接受请求 Slave 发出停止命令 Slave 继续处理
现存请求处理完毕结束运行确认所有 Slave 结束 Master 本身结束运行
到此为止,Unicorn 整体重启过程完成服务停止时间
6.
信号 2 UNIX信号一览表具有代表性
     默认动作
Unicorn 重启部分我们到了信号
HUP
挂起 Term
这个概念对于 UNIX 操作系统了解 INT 键盘中断 Term
读者可能明白信号 UNIX 进程 QUIT 键盘终止 Core
通信手段之一信号只是用于传达某种 ILL 非法命令 Core
发生通知而已并不其他数据。 ABRT 程序 abort Core
FPE
浮点数异常 Core
信号种类 2 kill 命令可以 KILL 强制结束不可捕获) Term
发送进程。 SEGV 非法内存访问 Core
BUS
总线错误 Core
$ kill -QUIT <
进程 ID> 另一无连接管道
PIPE Term
数据
kill 命令没有指定信号默认
ALRM
计时器信号 Term
发送 INT 信号程序可以使用 kill 系统
TERM
结束信号 Term
用来发送信号,Ruby 用于调用 kill USR1 用户定义信号 1 Term
系统调用 Process.kill 方法。 USR2 用户定义信号 2 Term
CHLD
进程暂停结束 Ign
这些信号根据各自目的设有默认 STOP 进程暂停不可捕获) Stop
默认动作根据不同信号分为 Term ( CONT 进程恢复 Cont
)、Ign ( )、Core ( TSTP 来自 tty stop Stop
)、Stop (进程暂停)、Cont (进程恢复)5 TTIN 后台 tty 输入 Stop
TTOU
后台 tty 输出 Stop
如果程序对于信号配置专用处理过程
218
----------------------- Page 229-----------------------
4.5 Rack
Unicorn
(handler ),
可以这些信号进行特殊处置不过,KILL 信号 STOP 信号无法改变处理
因此即便软件配置特殊处理过程无法通过 TERM 信号结束可以
发送 KILL 信号强制结束
信号原本特定状况操作系统进程进行通知设计例如终端窗口按下
Ctrl+C ,
当前运行中的进程发送 SIGINT 。
然而信号不光可以用来发送系统通知经常用来外部进程发送命令这些信号
已经用户准备 USR1 USR2 自定义信号
Unicorn
充分运用信号机制刚才我们已经讲到 Slave 发送 QUIT 信号可以使
结束。Master/Slave 对于各个信号响应方式 3 其中有一些信号功能看起来
面目全非比如 TTIN ),算是“UNIX 流派有的风格
3 Unicorn信号响应
Master
     
HUP
重新读取配置文件重新载入程序
INT/TERM
立刻停止所有 Slave
QUIT
所有Slave 发送 QUIT 信号等待请求处理完毕结束
USR1
重新打开日志
USR2
系统重启重启完成当前Master 收到 QUIT 信号
TTIN
增加 Slave
TTOU
减少 Slave
Slave
     
INT/TERM
立即停止
QUIT
当前请求处理完毕结束
USR1
重新打开日志
信号可以通过 Shell 或者其他程序发送因此编写用于外部重启 Unicorn
容易
性能
http://github.com/blog/517-unicorn 专栏 Unicorn 性能 Mongrel 进行比较
默认状态 Unicorn ,性能已经优于 Mongrel 尽管 Thin Mongrel
219
----------------------- Page 230-----------------------
4 云计算时代编程
性能一些 Unicorn 不会甘拜下风
考虑 Unicorn 几乎全部 Ruby 编写 HTTP 报头解析一点可以
实现非常优秀性能此外刚才介绍 Unicorn 特点大多数情况
Unicorn
替代 Mongrel Thin 还是一定好处
不过,Unicorn 并非万能。Unicorn 适合请求处理时间应用对于应用
本身外部如数查询消耗时间情况不是适合
对于 Unicorn 棘手莫过于 Comet ①这种服务器基本处于待机状态根据
变化推送响应应用 Unicorn 由于请求需要进程处理这样造成
Slave
数量不足无法满足请求处理最终导致应用程序整体卡死对于这样应用程序
使用其他一些技术使得通过少量资源能够接受大量连接
为了弥补 Unicorn 这些缺点出现名叫“Rainbows!”② 项目 Rainbows!
可以 N 进程分配 M 请求从而缓和大量连接有限进程之间落差
策略
综上所述,Unicorn 关键不是 HTTP 服务器主动进行负载均衡而是采用完成
工作 Slave 主动获取请求模式对于 Slave 之间任务分配通过操作系统任务切换
完成这个案例表明大多数情况与其身为用户应用 HTTP 服务器进行拙劣
分配还不如这种工作交给内核这个资源管理第一负责人完成
另一关键 UNIX 操作系统功能充分利用例如通过 fork 系统调用以及背后
Copy-on-Write
技术加速 Slave 启动。UNIX 最近加入线程功能,Unicorn 选择依赖
线程而是已经过气进程技术加以限度充分利用线程由于可以共享内存空间
性能上来进程更加有利一些反过来说正是因为内存空间共享使得容易
各种问题因此 Unicorn 干脆放弃使用线程方法
如此,Unicorn 充分利用 UNIX 操作系统长年积累下来智慧保持简洁同时
充分性能管理
① Comet :
一种Web 服务器 Web 客户端发送数据推送(push )技术。(
② Rainbows!
项目官方网站:http://rainbows.rubyforge.org/ 。(
220
----------------------- Page 231-----------------------
4.5 Rack
Unicorn
近年来由于考虑 C10K 问题客户端超过问题采用事件驱动模型
Web
应用程序用户应用程序水平变得越来越复杂 Unicorn 通过复杂工作
操作系统完成从而实现简洁架构因为事件处理任务切换等等本来就是操作
具备功能当然 Unicorn 客户端并发连接处理还是存在极限如果请求
有可能处理不过我们可以使用反向代理多个 Unicorn 系统捆绑起来从而
实现横向扩展 7 )。
客户端浏览器
互联网
Apache(
反向代理
Unicorn Unicorn Unicorn Unicorn
Slave Slave Slave Slave
Web
应用程序
7 Unicorn 横向扩展
小结
Unicorn
限度利用 UNIX 优点同时实现高性能管理此外采用
模式线程模式模式模式通过追求实现简洁实现优秀特性对于
一点非常喜欢今后随着服务器多任务处理需求不断增加 Unicorn 这样简洁
越来越体现价值
221
----------------------- Page 232-----------------------
4 云计算时代编程
云计算时代编程后记
首先关于 HashFold 一些补充。HashFold 首次出现专题连载
现在已经时间不但没有引起广泛关注倒是完全消亡对于使用
Hash
(stream )这个主意觉得有趣有趣还是无法推动潮流
不过文章内容本身作为使用线程进程进行数据处理实例还是足够
价值因此还是决定
现在反观 HashFold ,大量数据处理比起运用 Hash 这样容器数据结构
模型感觉处理方式印象一些此外,HashFold 真的普及
的话重要需要 Hadoop 这样 MapReduce 高性能实现纸上谈兵恐怕
不会什么结果
思考今后云计算时代编程这个话题时候介绍内容应该作为
基础技术继续存在下去程序员看到表面抽象程度应该越来越
今后随着云计算普及节点数量不断增加节点进行管理几乎
变成不可能完成事情于是节点成了性能实现单位”,作为
硬件节点概念逐步忽略这样环境恐怕不会进行指定节点
方式通信这样程序设计说不定 Linda 这个系统提供黑板模型
引起大家关注
这种模型利用一块共享黑板称为 tuple space ),上面信息需要
上面信息完成工作结果黑板 Ruby 利用 dRuby 提供
Rinda 系统
虽然 Linda 20 世纪 80 年代古老技术云计算潮流这个领域
不断要求我们温故而知新
222
----------------------- Page 233-----------------------
4.5 Rack
Unicorn
支撑大数据数据
存储技术 5
223
----------------------- Page 234-----------------------
5.1
- 存储
- 存储(Key-value store )数据库一种云计算愈发流行今天- 存储
受到越来越关注关系数据库管理系统(RDBMS )代表现有数据库系统接近
极限 - 存储拥有超越这种极限可能性
- 存储通过对象对象映像保存数据具体原理我们稍后详细讲解
例如旅游预订网站乐天旅游”① 可以显示最近浏览酒店”,数据
ID”,酒店 ID (多个)”,通过用户 ID 相关保存用户浏览酒店 ID 。
对于熟悉 Ruby 读者可以这种方式理解 Ruby 内建 Hash 具有相同功能
不同,Hash 只能存在内存 - 存储数据库因此具备数据永久保存
能力
使用 - 存储方式数据库大多数数据查找技术使用列表这种数据结构
列表通过调用散列函数生成散列原始数据一一对应固定数值
映射通过散列确定数据存放位置列表中的数据无论如何增大查找数据
时间几乎固定不变因此一种非常适合大规模数据技术
讲解 - 存储我们基本工作方式 Hash 开始
Hash

一般意义,Hash (列表通过创建配对快速找到一种
结构
作为例子我们看一看 Ruby Hash 拥有 147 方法不过本质可以通过
3 方法描述
乐天旅游トラベル ):http://travel.rakuten.co.jp/
225
----------------------- Page 235-----------------------
5 支撑大数据数据存储技术
hash[key]
hash[key] = value
hash.each {|k,v| ...}
hash[key]
方法用于 Hash 取出返回 key 对象相对 value 对象找不到 key
相对对象返回 nil 。hash[key] = value 方法用于 key 对象相对 value 对象存放
Hash 已经存在 key 相对对象 value 覆盖最后 hash.each 方法
顺序遍历 Hash 中的 -
也就是说,Hash 对象用于保存 key 对象 value 对象之间对应关系数据结构这种
结构其他编程语言有时称为 Map (映像或者 Dictionary (字典)。觉得字典
概念描述 Hash 性质合适因为字典就是词条查询对应释义工具
DBM

Hash
中的数据只能存在内存程序运行结束之后消失为了超越进程范围
保存数据可以使用 Ruby “DBM”这样 - 存储方式
DBM
用法 Hash 几乎一模一样以下这些区别
‰ key
value 只能使用字符串
‰
创建 DBM 对象需要指定用于存放数据文件路径名称
‰
数据存在文件
可以超越进程范围保存数据特性编程世界称为永久性
(persistence )。
数据库 ACID 特性
下面我们分析一下为什么云计算时代 - 存储模型受到关注”。
问题关键在于 RDBMS 数据库具备 ACID 性质我们这里开始。ACID
4 单词首字母缩写它们分别:Atomicity (原子)、Consistency (一致性)、Isolation
隔离Durability (持久)。
所谓 Atomicity ,对于数据操作允许全部完成完全改变状态
226
----------------------- Page 236-----------------------
5.1 
- 存储
中的一种允许任何中间状态因为操作无法进一步进行分割所以原子这个
表现例如银行进行汇款操作时候 A 账户 B 账户汇款 1 假设当中由于
某些原因发生中断这时 A 账户已经 1 B 账户没有存入 1 就是
中间状态
A 账户余额 1 B 账户余额增加 1 操作如果
成了其中的话账户余额发生矛盾
所谓 Consistency ,指数状态必须永远满足给定条件性质例如给定
账户余额永远正数条件,“取出大于账户余额款项操作无法执行
所谓 Isolation ,保持原子一系列操作中间状态不能其他事务进行干涉
性质由此可以保持隔离避免其他事务产生影响
所谓 Durability ,保持原子一系列操作完成时结果保存并且不会丢失
性质
整体,ACID 非常重视数据完整性 RDBMS 正是保持这样 ACID 特性不断
进化至今
近年来满足这样 ACID 特性变得越来越困难正是 RDBMS 极限也就是
我们希望通过 - 存储克服问题
CAP
原理
近年来人类可以获得信息持续增加如此大量数据无法存放单独一块硬盘
无法单独计算机进行处理因此通过计算机集合进行处理成为必然趋势
这样一来实际运营发生延迟故障问题计算机之间通信需要通过网络
网络一旦饱和产生延迟
计算机机器发生故障概率随之升高数量级数据中心据说
每天都会计算机发生故障 由于延迟故障原因导致计算机集合之间连接
切断原本集合分裂若干小的集合
组成计算环境计算机数量达到以上有些情况甚至达到规模
ACID
特性满足换句话说,ACID 不可扩展
227
----------------------- Page 237-----------------------
5 支撑大数据数据存储技术
有人提出 CAP 原理大规模环境
‰ Consistency (
一致性
‰ Availability (

‰ Partition Tolerance (
分裂容忍
性质只能同时满足其中
大规模数据库如何保持 CAP ,一般系统情况进行类推因为大规模系统
延迟故障分裂家常便饭
我们平常接触规模网络环境计算机故障发生对于数万
十万规模集群这样常识无效这样数量级定律
一样,“只要存在故障可能性一定发生故障而且时间点)”。
据说 CAP 原理 已经通过数学方法到了证明。CAP 中的 C 满足 ACID 重要因素
如果 CAP 原理真的成立的话我们可以推断 RDBMS 这样传统数据库大规模环境
无法达到期望值或者无法充分发挥性能)。真是难题
CAP
解决方案——BASE
根据 CAP 原理,C (一致性)、A (P (分裂容忍之中必须舍弃
如果舍弃分裂容忍的话那么只有选择要么根本不会发生分裂要么发生分裂
能够其中一方失效
根本不会发生分裂意味着需要能够处理大规模数据高性能计算机而且
计算机发生故障意味着整个系统停止运行现代数据规模计算机处理
不可能完成因此扩展角度不是有效方案
此外发生分裂例如计算机集群分割小的集群区分哪一个
并非易事如果准备所在集群真身”,的确可以做到
如果发生故障的话等于整个系统发生故障风险大大增加
分布式系统这样局部故障导致整体故障要害称为 Single point of failure
单一故障),分布式系统需要极力避免
228
----------------------- Page 238-----------------------
5.1 
- 存储
不能舍弃
那么舍弃 A (这个选择如何这里关键字等待”。也就是说发生
分裂服务需要停止等待分裂恢复另外为了保持一致性必须等待所有数据
完成记录
然而用户到底能够等待时间仅仅作为用户相当没有耐心
秒钟开始感到烦躁如果分钟没有响应再也不会使用这个服务假设
分裂延迟原因由于机器故障便是准备完善备份机制想要秒钟之内恢复
几乎不可能所以结论就是除非不怎么用得上服务否则不能舍弃
那么现在剩下 C (一致性舍弃一致性是否现实仔细的话现实世界
严密一致性几乎存在例如,A 包裹 B ,现实世界不可能瞬间
A
需要包裹交给物流公司然后通过卡车途径 B 这个过程需要消耗一定
时间 Atomicity )。而且配送中的状态可以追踪Isolation ),运输过程如果发生
事故包裹可能损坏 Consistency )。即便损坏遗失保险此次运输交易
本身不可能一笔勾销”。
即便现实世界如此残酷我们还是进行各种交易事务)。这样看来便是某种
无法满足一致性环境数据处理能够完成例如网上商城商品信息页面
明明”,实际提交订单时候变成缺货”,这种已经家常便饭
不会产生什么问题银行汇款不同其实大多数处理需要严格遵循 ACID 特性
这样环境,BASE 这样思路也许更加合适。BASE 下列英文缩写
‰ Basically Available
‰ Soft-state
‰ Eventually consistent
ACID
无论任何情况保持严格一致性一种比较悲观模式实际上
一致不会经常发生因此 BASE 比较重视(Basically Available ),追求状态
严密(Soft-state ),不管过程中的情况如何只要最终能够达成一致即可(Eventually
consistent )。
这种比较乐观模式也许适合大规模系统开始觉得 BASE
这个缩写似乎有点牵强其实 BASE (ACID (相对里面包含文字
游戏
229
----------------------- Page 239-----------------------
5 支撑大数据数据存储技术
大规模环境 - 存储
下面进入—— - 存储 - 存储优点可以通过给定
对应简单模式支撑相当大规模数据 - 存储之所以适合大规模数据
除了使用散列一点因为结构简单容易数据分布存储计算机
不过实际大规模 - 存储系统还是存在一些必须注意问题下面我们
大体框架探讨一下扩展 - 存储架构
分布 - 存储基本架构并不复杂计算机节点组成虚拟圆环其中
节点负责范围散列对应数据
应用程序客户端程序数据进行访问首先通过作为数据字符串计算
散列然后找到负责
0.0 1.0
节点直接节点
请求取出或者存放数据即可 散列4.2
12.2 4.0
1 )。怎么样简单
应用程序
实现具备实用
扩展 - 存储 10.0 4.5
1  - 存储架构示例
系统需要注意问题还有 通过散列判断存放数据节点
下面我们看看这个 6.8 5.1 直接进行访问为了提高
两端相邻节点拥有数据副本
系统具体实现
先声 一下这里讲解扩展 - 存储架构基本上是以乐天开发 ROMA 基础
扩展 - 存储系统现有多种这里介绍架构不是唯一一种另外为了
讲解方便这里介绍内容实际 ROMA 实现有一些偏差
访问 - 存储
我们简单应用程序实现应用程序执行处理并不大体上可以
初始化访问获取保存)”步骤
首先初始化应用程序初始化需要指定几个构成 - 存储系统节点指定
节点并不需要特殊节点只要参加 - 存储系统组成节点可以之所以指定多个
考虑其中至少应该存活
230
----------------------- Page 240-----------------------
5.1 
- 存储
应用程序顺序访问指定节点第一应答 1 ROMA访问请求
节点传达访问 - 存储请求接着建立连接 访问请求   
节点客户端发送哪个节点负责哪个范围散列” get 获取对应
set
设置对应
信息路由)。收到路由之后剩下访问操作
add
存在设置
比较简单根据获取数据计算散列
replace
存在设置
通过路由查询负责散列节点 节点 append 当前末尾附加
发送请求请求内容分为获取保存种类 prepend 当前开头附加
ROMA
支持请求 1 Hash 稍微复杂 delete 删除对应
一些。 inc 对应 1
del
删除对应
每次进行数据访问应用程序需要负责各个
散列节点建立连接并进通信这个通信过程通过完成通过
远程主机建立连接实际上需要开销。ROMA 早期原型每次需要建立
连接于是这个部分成了瓶颈导致系统无法发挥期望性能
所幸一般情况 - 存储访问具有局部性也就是说同一访问可能
连续发生这样情况(pooling )技术比较有效所谓就是使用
资源进行反复利用技术这个案例也就是一定数量连接进行反复利用
访问具有局部性情况连接效果非常
- 存储运用难免遇到由于延迟故障分裂导致某些节点无法访问
ROMA 应用程序持有记载组成 - 存储系统所有节点信息路由),
直接节点进行访问这种类型系统保持路由处于最新状态非常重要
ROMA
定期对路进行更新每隔时间客户端路由中的任意节点发出
最新路由信息请求
此外各个节点请求设置超时时间如果节点规定时间响应请求
路由删除
- 存储节点处理
应用程序相比组成系统节点行为十分复杂特别 ROMA 这样存在承担特殊
工作节点节点之间相互平等 P2P 系统
节点工作大体包括以下内容
231
----------------------- Page 241-----------------------
5 支撑大数据数据存储技术
‰
应对访问请求
‰
信息保存
‰
维护节点构成信息
‰
更新节点构成信息
‰
加入处理
‰
终止处理
正如 1 系统中的节点成了圆环其中节点结构 2
来自应用程序请求
相邻节点 控制部分 相邻节点
存储器
2 节点结构
存储器
存储器(storage )就是实际负责保存信息部分ROMA 存储器作为插件存在
通过启动设置可以存储器进行切换目前实现存储器包括下列这些
‰ RH
存储器信息存在 Ruby Hash 对象 中的存储器这种方式无法信息保存
文件因此 ROMA 整体运行停止信息消失
‰ DBM
存储器信息存在 DBM (实际上GDBM )中的存储器
‰ File
存储器信息存在文件中的存储器都会产生独立文件因此
目录可以用作索引
‰ SQLite3
存储器信息存在 SQLite3 中的存储器。SQLite3 一种有名公有领域
RDBMS 。
‰ TC
存储器信息存在 Mixi 开发 TokyoCabinet 中的存储器 ROMA 实际
最为常用一种
各个存储器定义“Roma::Storage::BasicStorage”定义采用模板方法形式
运用,ROMA 可以根据数据库所需特性选择合适存储器实际大多数案例
可以 TC 存储器解决
232
----------------------- Page 242-----------------------
5.1 
- 存储
读取
应用程序发起请求确认散列属于节点负责范围节点通过
数据进行保存
这个时候如果数据存在节点的话万一这个节点发生故障数据丢失
为了提高分裂容忍必须多个节点共同完成
如果重视响应速度的话 节点完成操作之后马上请求进行响应剩下
操作可以后台完成 ROMA 由于响应速度不是非常重视而是需要追求可靠
因此所有操作同步执行不过如果由于某些原因导致数据复制失败
后台重新尝试执行操作
如果由于应用程序持有路由信息过期原因导致请求属于节点负责
范围节点请求转发出去
读取处理差不多 由于需要出于冗余目的其他节点发出请求因此处理
方式更加简单
节点追加
分布式 - 存储系统优点就是运用灵活性数据过多访问速度下降只要
节点可以进行应对
追加节点需要指定现有节点作为入口然后启动节点启动节点
指定现有节点进行通信申请加入环状结构然后全体节点发送节点数据分配进行
请求刚刚启动节点并不包含任何数据启动指定永久保存数据库
情况除外),随着节点调整进行数据逐步分配节点
故障应对
作为扩展 - 存储系统重要恐怕就是故障容忍正如之前
随着组成系统计算机增加发生故障概率大幅度上升大规模系统即便
组成系统一部分计算机发生故障系统必须能够继续运行
233
----------------------- Page 243-----------------------
5 支撑大数据数据存储技术
发生频率最高应该计算机故障由于故障导致计算机系统消失
例子十分常见
由于故障导致节点失去响应应用程序尝试访问其他节点 ROMA 由于
数据总是存放多个节点因此通过路由可以找到其他替代节点
另一方面出现响应节点意味着数据冗余下降为了避免这种情况其他
节点需要消失节点排除然后重新组织节点结构根据需要相邻节点复制数据最终
维持数据平衡 节点结构信息通过定期更新发送应用程序作为整个 -
什么没有发生一般继续运行
比较麻烦情况暂时消失节点复活这种情况发生可能由于线
运营工作经常出现意外),或者由于网络故障导致大规模网络延迟这些应该还是
常见
简洁信条 ROMA 遇到这样情况已经分离节点完全舍弃如果出现
复活情况节点需要作为节点重新加入 ROMA 系统。ROMA 加入节点
更新路由重新数据进行分配
一种故障可能发生就是多个节点同时消失 ROMA 节点消失
一样这些节点舍弃多个节点同时消失情况可能发生冗余备份数据同时
丢失问题找回丢失数据不可能因此系统报错这种情况无法区分
数据开始存在还是由于大量节点消失导致数据丢失当然引发一些问题
失去东西总归无法必要进行任何特殊处理
话虽如此丢失数据这种作为数据库确实小的问题为了拯救数据
ROMA
提供命令 切断经由存储器文件数据重新传回 ROMA 。
操作只是存储器数据读取出来添加 ROMA 基本部分非常简单不过如果
上传数据有一些已经 ROMA 存在它们对应相同情况必须决定
其中解决冲突到底分离之后 ROMA 数据更新还是节点数据
由于某些原因没有反映 ROMA 数据无法判断
实际上 ROMA 数据附加叫做逻辑时钟”(logical clock )信息
一种每次数据更新进行累进计数器当上数据 ROMA 已经存在数据
冲突通过逻辑时钟可以判断应该一方数据为准
各种故障可能发生分裂情况也就是完全隔绝网络继续独立工作
意思 ROMA 应对这种故障只能分裂 ROMA 系统中的其中手动停止
234
----------------------- Page 244-----------------------
5.1 
- 存储
虽然这种手段非常原始分裂这样故障并不经常发生这样应对应该已经足够
分裂故障进行恢复上述情况一样通过使用存储器上传数据功能完成
终止处理
出人意料 P2P 结构麻烦操作居然终止进行终止操作首先
任意节点发送终止请求然后节点自动成为负责终止节点全体节点发送
终止声明收到声明之后节点停止接受请求当前时间点正在处理请求
全部完成之后存储器执行文件内存存储情况除外),完成终止节点发送
回复完毕结束节点进程
负责终止节点收到全部节点故障应答节点除外回复结束自身进程
至此,ROMA 系统运行全部停止
其他机制
除了上述讲到内容之外,ROMA 还有以下这些机制
1.
有效期
ROMA
中的数据设置有效期因此如果实现这个数据今天有效明天需要
删掉这样规则容易
2.
虚拟节点
为了节点之间分配调整进行数据传输更加高效系统采用若干组织
形成虚拟节点机制
3.
散列
1 ,ROMA 使 环状节点分布浮点小数散列实际上算法使用
SHA-1 ①
散列 Merkle 散列② ,这种方式效率
① SHA-1
SHA 系列成员之一。SHA (Secure Hash Algorithm ,安全散列算法美国国家安全局设计
散列算法美国国家标准广泛应用各种安全协议。SHA 系列包括 SHA-1、SHA-224、SHA-256、
SHA-384
SHA-512 5 成员
散列(hash tree )用于存放大量数据文件散列信息一种数据结构用于数据进行验证这种
数据结构列表链表组合散列算法一种扩展散列称为 Merkle 命名来源于 Ralph
Merkle(1952— ),
加密算法创始人之一同时研究人体冷冻技术分子纳米技术科学家
235
----------------------- Page 245-----------------------
5 支撑大数据数据存储技术
性能应用实例
乐天旅游最近浏览酒店和乐市场浏览历史功能采用
ROMA ,
用户访问历史记录存在 ROMA 中的。ROMA 基本上 Ruby 编写
但是提供性能足够支持日本网站应用
小结
除了 ROMA 之外还有 - 存储系统实现方式它们具备各自特点由于
这些项目大多数开源因此通过阅读源代码研究一下或许有意思
乐天市场:http://www.rakuten.co.jp/
236
----------------------- Page 246-----------------------
5.2 NoSQL

说起 NoSQL ,这里不是某种数据库软件这个名字所谓 NoSQL ,象征
数据库 SQL 语言相对出现名词包括 - 存储在内所有关系数据库
统称不过关系数据库情况还是非常有效因此有人批判 NoSQL 这个
体现不再需要 SQL”这个印象过于强烈主张应该解释“Not Only SQL”(不仅
SQL )(
1 )。
NoSQL
数据库
关系数据库
面向文档 面向对象
- 存储
数据库 数据库
查询 查询
SQL SQL
以外语言JavaScript
1 NoSQL 数据库
属于 NoSQL 数据库主要 ROMA (Rakuten On-Memory Architecture )这样-
存储数据库以及接下来介绍 MongoDB 这样面向文档数据库
RDB
极限
大规模环境尤其是作为流量网站后台一般认为关系数据库性能存在极限
因为关系数据库必须遵守 ACID 特性
ACID
Atomicity (原子)、Consistency (一致性)、Isolation (隔离Durability (
单词首字母缩写
一下一种数据库软件 NoSQL ,不过我们话题不是这个软件。(
237
----------------------- Page 247-----------------------
5 支撑大数据数据存储技术
所谓 Atomicity ,对于数据操作允许全部完成完全改变状态
中的一种允许任何中间状态因为操作无法进一步进行分割所以原子这个
表现
所谓 Consistency ,指数状态必须永远满足给定条件性质事务无法
满足给定条件执行取消
所谓 Isolation ,保持原子一系列操作中间状态不能其他事务进行干涉
性质由此可以保持隔离避免其他事务产生影响
所谓 Durability ,保持原子一系列操作完成时结果保存并且不会丢失
性质
数据访问频率增加,ACID 特性成了导致性能下降原因因为随着数据
访问频率增加维持 ACID 特性带来开销越来越明显
例如为了保持数据一致性需要访问进行并发控制这样必然导致接受
并发访问数量下降如果数据库分布服务器为了保持一致性带来通信开销
导致性能下降
当然如果适当方式数据库分割从而控制访问频率数据方面进行优化
的话一定程度可以应对这个问题大规模环境使用关系数据库一般水平分割
垂直分割分割方式
所谓水平分割就是中的数据直接分割多个例如对于 mixi ①
社交媒体(SNS )网站如果用户编号奇数用户信息编号偶数用户信息
应该比较有效
相对所谓垂直分割就是中的某些字段分离其他 SNS 网站
举例的话相当于按照日记”、“社区功能对数进行分割
通过这样分割可以单独关系数据库访问量和数进行控制但是这样
维护难度随之增加
NoSQL
数据库解决方案
NoSQL
之所以受到关注就是因为可以成为解决关系数据库极限问题一种方案
关系数据库相比,NoSQL 数据库具有以下优势 2 ):
① mixi :http://mixi.jp/ ,
日本社交网站
238
----------------------- Page 248-----------------------
5.2 NoSQL
限定查询方式 保持最终一致性
Key Value
开始 结束
通过限定访问数据 通过放宽一致性保持
方式提高速度 规则提高速度
2 NoSQL 优点
‰
限定访问数据方式
大多数 NoSQL 数据库数据访问方式限定通过查询条件查询相对
查询对象数据一种由于存在这样限定可以实现高速查询而且大多数
NoSQL
数据库可以单位进行自动水平分割
此外 memcached 这样永久保存数据只是作为缓存使用数据库算是
一种数据访问方式限定
‰
放宽一致性原则
保持大规模数据库尤其是分布式数据库一致性需要开销十分显著因此大多
NoSQL 数据库遵循“BASE”原则
所谓 BASE , Basically Available、Soft-state Eventually consistent 缩写,ACID 无论
任何情况保持严格一致性实际上数据一致不会经常发生因此 BASE 比较
(Basically Available ),追求状态严密(Soft-state ),不管过程中的情况如何
只要最终能够达成一致即可(Eventually consistent )。
如果遵循 BASE 原则那么用于保持一致性开销可以得到控制标榜 ACID 关系
数据库做出这样决断
形形色色 NoSQL 数据库
NoSQL
数据库只是统称其中包含各种各样数据库系统大体上可以分为以下
‰
- 存储数据库
‰
面向文档数据库
‰
面向对象数据库
- 存储一种进行关联简单数据库查询方式基本上限定通过进行
239
----------------------- Page 249-----------------------
5 支撑大数据数据存储技术
可以理解关系数据库只能提供拥有特定记录进行查询功能而且还是
限制UNIX 早就提供DBM 这种简单数据库分类上来可以 - 存储
但是 NoSQL 这个语境所谓 - 存储一般分布式 - 存储系统符合这样
- 存储数据库包括“memcached”、“ROMA”、“Redis”、“TokyoTyrant”
所谓面向文档数据库对于 - 存储部分存储不是单纯字符串数字
而是拥有结构文档单纯 - 存储不同由于可以保存文档结构因此可以基于
内容进行查询
例子会员清单包括姓名地址电话号码现在从中查找名字
会员也许乍看之下关系数据库应用方式一样但是不同在于
文档数据库对于存放会员信息文档会员文档结构可以不同因此
查找名字松本会员实际上相当于具备名字这个属性属性松本
进行查询这种情况文档通常采用 XML (eXtended Markup Language )JSON
(JavaScript Object Notation )
格式面向文档数据库包括CouchDB、MongoDB 以及各种 XML
数据库
所谓面向对象数据库面向对象语言中的对象直接进行永久保存也就是计算机
关机之后对象不会消失意思 - 存储和面文档数据库感觉个数
大多数面向对象数据库看起来只是对象进行永久保存系统而已当然面向对象
提供对象查询功能面向对象数据库例子 Db4o 、ZopeDB 、ObjectStore
使 ObjectStore。 C++
ObjectStore
编写 CAD 软件真是怀念起来那个时候 ObjectStore 支持分布式环境
对于跨越多数创建对象功能以及不再使用对象进行回收分布式垃圾回收功能
自己力量实现知道现在是不是有了正式支持
关系数据库角度这里我们暂且面向对象数据库 NoSQL
一种至少有些过时经验面向对象数据库主要目的提升一些数据结构
比较复杂规模数据库访问速度其他 NoSQL 数据库相比扩展方面不是
擅长
面向文档数据库
下面我们介绍一下面向文档数据库所谓面向文档数据库可以理解 JSON 、
XML
这样文档直接进行数据库形式特点包括需要 schema (数据库结构定义),
240
----------------------- Page 250-----------------------
5.2 NoSQL
支持计算机进行并行处理水平扩展
1. CouchDB
CouchDB
可以面向文档数据库先驱。CouchDB 特点 RESTful 接口以及采用
Erlang
进行实现
CouchDB
提供 遵循 REST (Representational State Transfer ,表征状态转移模型接口
因此即便没有特殊客户端使用 HTTP 可以数据进行插入查询更新删除操作
关系数据库不同 数据不必拥有相同结构可以各自拥有一些自由元素
CouchDB
通过 JSON 记录进行描述
此外 CouchDB 一部分逻辑可以 JavaScript 编写插入数据库整体
数据库应用程序之间区别不是那么明确大多数习惯数据库负责数据应用
负责逻辑”,此时也许需要自己这种模式出来
出人意料数据连结(Join )之类传统数据库通过SQL 可以轻松完成
查询 CouchDB 做不到因此传统关系数据库可能觉得四处碰壁
如果能够完全运用 CouchDB 功能应用程序设计可以变得十分简洁
这种数据库 Erlang 实现一点值得关注。Erlang 一种并行计算特别
函数语言分布式计算并行计算方面程序设计一直强项因此 CouchDB
这样需要通过机器分布协调应对大量访问场景应该能够充分发挥 Erlang 性能
2. MongoDB
CouchDB 相比,MongoDB 大概接近传统数据库。MongoDB 宣传口号 Combining
the best features of document databases, key-value stores, and RDBMSes ,
结合 CouchDB
这样文档数据库 - 存储数据库关系数据库优点真是颇具挑战目标
MongoDB
除了具备事务功能之外确实提供关系数据库非常接近易用性此外
C++、C#、JavaScript 、Java 、各种 JVM 语言、Perl 、PHP 、Python 、Ruby 语言提供
访问驱动程序一点非常重要有了这样支持语言选择没有什么顾虑
MongoDB
安装
如果使用操作系统发行版本提供 MongoDB 软件包那么安装非常容易
Debian 软件包名字叫做 mongodb 。
241
----------------------- Page 251-----------------------
5 支撑大数据数据存储技术
即便没有提供软件包安装并非只要访问 1  MongoDB提供编译版本
系统平台
MongoDB
:http://www.mongodb.org/
Mac OS X 32

downloads ,
找到对应二进制下载可以提供官方 Mac OS X 64
编译版本系统平台 1 。 Linux 32
Linux 64

Windows 32

选用 Linux 32 版本下载 tar.gz 文件解压 Windows 64
Solaris x86
目录结构如下: Solaris 64
GNU-AGPL-3.0 (
许可协议
README (
说明文件
THIRD-PARTY-NOTICES (
第三方依赖关系信息
bin/ (
二进制文件
include/ (
文件
lib/ (
文件
MongoDB
许可协议 GNU-AGPL-3.0。AGPL 这种协议可能大家怎么听说
AFFERO GENERAL PUBLIC LICENSE
缩写简单基本条款 GPL 差不多的区别
有一点就是软件通过网络进行使用情况需要提供源代码用于商业用途
情况如果不想公开源代码貌似可以购买商用许可
bin
目录包含 MongoDB 数据库服务器客户端工具可执行文件只要这些
复制 /usr/bin Path 搜索目录可以完成安装如果需要自行编译客户端
程序的话需要安装 include 目录中的文件lib 目录中的文件
如果没有使用操作系统 CPU 相对编译版本需要下载源代码自行编译
不过,MongoDB 依赖准备起来有点麻烦如果 Ubuntu 源代码进行编译
可以参考这里资料英文):http://www.mongodb.org/display/DOCS/Building+for+Linux 。
Ruby 访 MongoDB , Ruby
RubyGems
可以轻松完成安装。RubyGems Ruby 各种应用程序设计软件包管
系统使用起来非常方便如果没有安装 RubyGems 的话这个机会赶紧安装
Debian
Ubuntu 输入下列命令进行安装
$ sudo apt-get install ruby rubygems
表示换行
RubyGems 安装 MongoDB Ruby 驱动程序可以输入下列命令
$ sudo gem install mongo
242
----------------------- Page 252-----------------------
5.2 NoSQL
启动数据库服务器
启动数据库服务器命令 mongod ,作为参数需要指定数据库存放路径以及 mongod 监听
连接口号默认口号 27017 。指定数据库路径选项“--dbpath”,指定口号
“--port”。例如如果创建“/var/db/mongo”目录希望数据库存放在此可以
下面命令启动数据库服务器假设 mongod 所在路径能够 Path 找到如果不能的话
需要指定绝对路径):
$ sudo mongod --dbpath /var/db/mongo
服务正常启动显示“waiting for connections on
port 27017 ”
这样消息屏幕截图 1 )。
MongoDB 进行操作需要使用 mongo
如果为数服务器指定默认端口
mongo 命令需要指定 --port 参数
终端控制台下列命令启动 屏幕截图 1 MongoDB 启动样子
mongo :
$ mongo
MongoDB shell version: 1.3.1
url: test
connecting to: test
type "exit" to exit
type "help" for help
>
这样连接成功这个命令可以通过交互方式对数进行操作对于学习 MongoDB
有帮助此外对于数据库规模调整修改十分方便
不过 mongo 命令没有提供编辑功能如果配合使用支持编辑功能 rlwrap 命令
方便
$ rlwrap mongo
上述格式启动可以 mongo 命令增加编辑功能这样不仅输入行进编辑
查询输入历史非常方便
Debian Ubuntu 可以下列命令安装 rlwrap :
$ sudo apt-get install rlwrap
243
----------------------- Page 253-----------------------
5 支撑大数据数据存储技术
MongoDB
数据库结构
MongoDB
结构分为数据库(database )、集合(collection )、文档(document )
mongo
命令输入“show dbs”可以显示当前连接数据库服务器管理数据库清单
> show dbs
admin
local
我们可以看到服务器管理数据库 admin local 对数操作
当前数据库进行连接显示消息,“connecting to:”表示就是当前数据库
当前数据库可以使用“db”命令
> db
test
这里数据库包含若干集合集合相当于关系数据库概念关系
数据库中的拥有各自结构定义(schema ),结构定义决定记录包含怎样
数据以及这些数据排列顺序因此记录遵循 schema 定义具备完全相同结构
对于结构 MongoDB 数据库虽然集合包含相当于记录文档
文档并不具备相同结构而是能够存放可以 JSON 进行描述任意数据一般来说
同一集合倾向于保存结构相同文档 MongoDB 并非强制
这就意味着随着应用程序开发进行对于数据库数据结构变化可以灵活做出
应对 Ruby on Rails 开发一旦数据库结构发生变化必须精力编写数据
脚本这样苦差事 MongoDB 完全可以避免
数据插入查询
关系数据库创建需要对表结构进行明确定义执行创建操作
更加灵活 MongoDB 需要这么麻烦 mongo 命令中用 use 命令可以切换当前
如果 use 命令指定存在数据库自动创建数据库
> use linux_mag
switched to db linux_mag
而且如果存在集合保存文档的话自动创建集合
244
----------------------- Page 254-----------------------
5.2 NoSQL
> db.articles.save({
... title: "
技术剖析 ",
... author: "matz"
... })
其中“…” mongo 命令表示提示通过这样命令我们 linux_mag 数据库
articles 集合插入文档
> show collections
articles
system.indexes
下面我们查询一下这个文档查询文档需要使用集合 find 方法
> db.articles.find()
{ "_id" : ObjectId("4b960889e4ffd91673c93250"), "title" : "
技术剖析 ",
"author" : "matz" }
保存数据自动分配名为“_id ”唯一 ID 。find 方法可以指定查询条件
> db.articles.find({author: "matz"})
{ "_id" : ObjectId("4b960889e4ffd91673c93250"), "title" : "
技术剖析 ",
"author" : "matz" }
如果指定 JavaScript 对象作为 find 方法参数返回与其属性匹配文档
这里我们数据库只有文档如果多个匹配文档的话自然返回多个结果
如果希望返回符合条件文档可以 findOne 方法代替 find 方法
JavaScript 进行查询
mongo
命令重要一点可以自由运行 JavaScript 。mongo 接受命令除了
help、exit
一部分命令之外其余 JavaScript 语句甚至可以,mongo 命令本身就是
交互 JavaScript 解释器刚才例子出现
db.articles.find()
写法正是 JavaScript 方法调用形式由于支持 JavaScript ,因此我们可以自由进行一些
简单计算结果赋值变量甚至 for 语句进行循环
> 1 + 1
2
> print("hello")
hello
下面我们 JavaScript 为数填充一定规模数据
245
----------------------- Page 255-----------------------
5 支撑大数据数据存储技术
> for (var i = 0; i < 1000000
; i++) { ... db.bench.save( { x:4, j:i } ); ... }
相当时间之后我们创建包含 100 文档 bench 集合接下来
试试看查询
> db.bench.findOne({j:999999})
{ "_id" : ObjectId("4b965ef5ffa07ec509bd338e"), "x" : 4, "j" : 999999 }
电脑查询这个结果差不多 1时间因为 100文档全部查询一遍
所以这个速度不是于是我们创建索引
> db.bench.ensureIndex({j:1}, {unique: true})
这样我们 j 这个成员创建索引查询一次试试看
> db.bench.findOne({j:999999})
{ "_id" : ObjectId("4b965ef5ffa07ec509bd338e"), "x" : 4, "j" : 999999 }
创建索引之前按下回车键返回结果觉得一会儿现在瞬间可以得到结果
索引效果非常明显
mongo 命令可以使用 JavaScript 这样一种完全编程语言对数进行操作感觉
真是不错也许因为没有工作使用 SQL 原因觉得需要 SQL 这样一种
完全语言编写算法进行操作关系数据库觉得习惯 之下还是 MongoDB
感觉更加亲近一些个人喜好自然希望 mongo 命令可以 Ruby 数据
进行操作话说,2012 4 ,AvocadoDB ①宣布集成 mruby ,值得期待
高级查询
MongoDB 使用 find 或者 findOne 方法指定对象作为条件返回成员名称
匹配文档严格来说,find 方法返回符合条件结果相对游标 findOne
返回第一找到符合条件文档
SQL
MongoDB
查询方式学习一下 MongoDB 查询编写方法刚才出现查询符合条件
例子 SQL 编写的话应该下面这样
① AvocadoDB
2012 5 名为 ArangoDB: http://www.arangodb.org/
246
----------------------- Page 256-----------------------
5.2 NoSQL
SELECT FROM bench*
WHERE x = 4
查询 MongoDB 查询这样
> db.bench.find({x: 4})
如果希望选出特定成员字段), SQL
SELECT j FROM bench WHERE x = 4
MongoDB
的话
> db.bench.find({x: 4}, {j: true})
刚才我们查询条件等于”,如果比较大小当然可以例如,“x 大于等于 4”
这样条件 SQL 查询可以
SELECT j FROM bench WHERE x >= 4
MongoDB 的话
> db.bench.find({x: 4}, {j: {$gte: 4}})
比较条件不是等于上面这样使用“$”开头比较操作符表达。MongoDB
可以使用比较操作符 2
2 比较操作符
        
$gt {$gt: val}
大于 val
$lt {$gt: val}
小于 val
$gte {$gte: val}
大于等于 val
$lte {$lte: val}
小于等于 val
$ne {$ne: val}
等于 val
$in {$in: val}
包含 val
$nin {$nin: val}
包含 val
$mod {$mod: [n, m]}
除以n 余数m
$all {$all: ary}
包含 ary 所有元素
$size {$size: n}
数组长度 n
$exists {$exists: true}
存在
$exists {$exists: false}
存在
$not {$not: cond}
否定条件
正则表达式 / ^foo 正则匹配
str 作为 JavaScript 进行求值
$where {$where: str}
this
引用文档
247
----------------------- Page 257-----------------------
5 支撑大数据数据存储技术
我们刚才已经 find 方法返回不是文档本身而是游标(cursor )。执行
查询得到多个匹配结果某些情况返回结果数量可能想象这时我们可以使
count 、limit、skip、sort 方法
count
方法可以返回游标关联结果大小
> db.bench.find().count()
1000000
limit
方法可以结果大小限制游标开始位置指定数量文档 3 )。
skip
方法可以使游标跳过指定数量记录 4 )。配合使用 limit skip ,可以 Google
搜索页面一样轻松实现 n 结果单位结果进行分页操作
> db.bench.find().limit(10) > db.bench.find().skip(10).limit(10)
{ "_id" : ..., "x" : 4, "j" : 0 } { "_id" : ..., "x" : 4, "j" : 10 }
{ "_id" : ..., "x" : 4, "j" : 1 } { "_id" : ..., "x" : 4, "j" : 11 }
{ "_id" : ..., "x" : 4, "j" : 2 } { "_id" : ..., "x" : 4, "j" : 12 }
{ "_id" : ..., "x" : 4, "j" : 3 } { "_id" : ..., "x" : 4, "j" : 13 }
{ "_id" : ..., "x" : 4, "j" : 4 } { "_id" : ..., "x" : 4, "j" : 14 }
{ "_id" : ..., "x" : 4, "j" : 5 } { "_id" : ..., "x" : 4, "j" : 15 }
{ "_id" : ..., "x" : 4, "j" : 6 } { "_id" : ..., "x" : 4, "j" : 16 }
{ "_id" : ..., "x" : 4, "j" : 7 } { "_id" : ..., "x" : 4, "j" : 17 }
{ "_id" : ..., "x" : 4, "j" : 8 } { "_id" : ..., "x" : 4, "j" : 18 }
{ "_id" : ..., "x" : 4, "j" : 9 } { "_id" : ..., "x" : 4, "j" : 19 }
3 limit 方法执行结果 4 skip 方法执行结果
sort
方法可以指定成员查询结果进行排序 5 )。
> var c = db.bench.find()
> c.skip(10).limit(10).sort({j: -1})
{ "_id" : ..., "x" : 4, "j" : 999989 }
{ "_id" : ..., "x" : 4, "j" : 999988 }
{ "_id" : ..., "x" : 4, "j" : 999987 }
{ "_id" : ..., "x" : 4, "j" : 999986 }
{ "_id" : ..., "x" : 4, "j" : 999985 }
{ "_id" : ..., "x" : 4, "j" : 999984 }
{ "_id" : ..., "x" : 4, "j" : 999983 }
{ "_id" : ..., "x" : 4, "j" : 999982 }
{ "_id" : ..., "x" : 4, "j" : 999981 }
{ "_id" : ..., "x" : 4, "j" : 999980 }
5 sort 方法执行结果
248
----------------------- Page 258-----------------------
5.2 NoSQL
这样我们完成成员 j 降序排列操作前面 skip(10).limit(10) 结果相比,j
不同由于 sort 方法整个查询结果进行排序因此对于查询结果这些方法
执行顺序实际调用顺序无关总是按照① sort ② skip ③ limit 顺序执行
数据更新删除
只有文档插入查询并不构成数据库完整功能我们需要进行更新删除文档
插入我们使用 save 方法保存文档赋予 _id 成员因此保存文档
_id
存在覆盖相应 _id 文档
也就是说 find findOne 方法取出文档取出文档(JavaScript 对象进行修改
再次调用 save (只有_id 成员不能修改的话覆盖原来文档
MongoDB 存在事务概念因此总是最后数据为准。MySQL 开始
支持事务时候还是非常有用由此可见,Web 应用中的数据库系统即便支持事务
不是问题。MongoDB 虽然支持事务可以支持原子操作(atomic operation )
乐观并发控制(optimistic concurrency control )。实现原子操作乐观并发控制可以使用
update
方法
update
支持原子操作 3 原子操作名称是以“$”开头例如 j
0 文档 x 增加 1,可以下面这样
> db.bench.update({j:0},{$inc:{x:1}})
3 update原子操作
        
$inc {$inc: {mem: n}}
mem n
$set {$set: {mem: val}}
mem 设置val
$unset {$unset: {mem: 1}}
删除 mem
$push {$push: {mem: val}}
数组 mem 添加val
$pushAll {$pushAll: {mem: ary}}
数组 mem 添加ary 元素
$addToSet {$addToSet: {mem: val}}
数组mem 包含val 添加val
$pop {$pop: {mem: 1}}
删除数组 mem 最后元素
$pop {$pop: {mem: -1}}
删除数组 mem 第一元素
$pull {$pull: {mem: val}}
数组 mem 删除所有val
$pullAll {$pullAll: {mem: ary}}
数组 mem 删除所有ary 中的元素
249
----------------------- Page 259-----------------------
5 支撑大数据数据存储技术
乐观并发控制
然而需要进行并发操作原子操作不够关系数据库一般通过
方式处理 MongoDB 没有这样机制。MongoDB 进行并发操作步骤如下
(1)
通过查询获取文档
(2)
保存原始
(3)
更新文档
(4)
原始包含 _id )作为第一参数更新文档作为第二参数调用update 方法
如果文档已经其他并发操作修改 update 失败
(5)
如果 update 失败返回 (1) 重新执行
这样 方式也就是利用 update 方法可以进行原子更新一点通过同时指定事务开始
更新手动实现相当于关系数据库事务处理功能这种方法前提
同一文档基本上不会同时修改预测也就是一种乐观似的事务机制
需要创建数据副本一点有些麻烦忽略一点的话实际上还是实用
简单例子我们刚才 $inc 那个例题乐观并发处理进行实现 6
> for (;;) {
... var d = db.bench.findOne({j:0})
... var n = d.x
... d.x++
... db.bench.update({_id:d._id, x:n}, d)
... if (db.$cmd.findOne({getlasterror:1}).updatedExisting) break
... }
6 乐观并发处理
250
----------------------- Page 260-----------------------
5.3
Ruby 操作 MongoDB
关系数据库为了保持基本 ACID 原则原子一致性隔离持久),需要
付出种种开销作为代价相对,MongoDB 这样面向文档数据库由于可以突破局限
因此工作起来显得比较轻快
MongoDB
具有下列这些主要特点
‰
JSON (JavaScript Object Notation )格式保存数据
‰
需要结构定义
‰
支持分布式环境
‰
乐观事务机制
‰
通过 JavaScript 进行操作
‰
支持多种语言进行访问
MongoDB
重要特点就是需要结构定义少有应用程序开发之前确定数据库
需要保存数据项目由于开发过程中的疏漏或者需求变化经常导致数据库结构
开发发生改变关系数据库(RDB )遇到这种情况每次需要重做数据库。Ruby
on Rails
可以通过 migration 方法 RDB 结构迁移提供支持即便如此这个过程依然相当
麻烦
MongoDB 本来没有结构定义即便数据库保存项目发生变化只要程序做出
可以当然已经存在数据包含新增项目做出应对容易
使用 Ruby 驱动
MongoDB
另一特点就是可以多种语言进行访问各种语言访问 MongoDB
称为驱动(driver )。MongoDB 分别 JavaScript 、C++、C#、Java 、JVM 语言、Perl 、
PHP 、Python
Ruby 提供相应驱动
MongoDB
JavaScript
JavaScript
编写不过因为有了支持各种语言驱动客户端除了发送服务器程序以外
251
----------------------- Page 261-----------------------
5 支撑大数据数据存储技术
可以自己喜欢语言编写
5.2 我们使用 mongo 命令访问数据库使用 JavaScript 对数进行操作不过
可以我们习惯 Ruby 操作数据库好了。RubyGems 提供相应 Ruby 驱动使
gem 命令可以轻松完成安装以下命令 Debian 为例):
% sudo gem install mongo
此外最好一并安装用于加速访问 C 语言① ,通过这个可以提升 MongoDB 服务
通信需要二进制 JSON”(BSON )处理速度
% sudo gem install mongo_ext
使用 MongoDB Ruby 驱动需要程序 mongo 进行 require 。此外 Ruby
需要 mongo 之前 rubygems 进行 require 。
require 'rubygems'
require 'mongo'
好了我们尝试访问一下 5.2 创建数据库服务器首先我们需要创建表示
连接 Mongo::Connection 对象
m = Mongo::Connection.new
=> <Mongo::Connection>#
在后面的程序示例,“=>”后面部分表示表达式求值结果返回表示是以 irb
基准由于版面进行大量省略此外相当于 ID 数值包括数字在内
实际情况有所不同。Mongo::Connection.new 可以可选参数第一主机名第二
口号相当于 mongo 命令中的“--host”“--port”参数
通过这个连接我们尝试获取服务器管理数据库清单
m.database_names
=> ["local", "admin", "test"]
删除个数可以对数连接对象调用 drop_database 方法
m.drop_database('test')
=> {"dropped"=>"test.$cmd",
"ok"=>1.0}
由于 mongo_ext 二进制 gems ,因此需要另外安装开发环境编译器以及编译文件)。(
252
----------------------- Page 262-----------------------
5.3 
Ruby 操作 MongoDB
对数进行操作
对数连接调用 db 方法可以获得个数对象创建数据库对象时间点
没有真正创建数据库
db = m.db("nikkei_linux")
=> <Mongo::DB>#
m.database_names
=> ["local", "admin", "test"]
通过调用数据库对象 collection 方法可以获取相应集合相当于关系数据库中的)。
如果获取集合存在创建集合但是和数一样实际集合
等到真正插入数据时候创建
coll = db.collection("articles")
=> <Mongo::Collection>#
db.collection_names
=> []
数据插入
使用 insert 方法或者 save 方法可以集合插入数据
coll.insert({
:title => "
技术剖析 ",
:author => "matz"})
=> ObjectID('4bbf93')
插入数据数据库集合真正创建出来
m.database_names
=> ["local", "admin", "nikkei_linux"]
db.collection_names
=> ["articles", "system.indexes"]
这样我们创建 nikkei_linux 数据库 articles 集合。system.indexes 集合 MongoDB
用于查询索引集合
数据查询
当然数据不光能够插入能够取出需要取出文档可以使用 find_
one
方法
253
----------------------- Page 263-----------------------
5 支撑大数据数据存储技术
coll.find_one()
=> {"_id"=>ObjectID('4bbf93'),
"title"=>"
技术剖析 ",
"author"=>"matz"}
这里我们没有指定查询条件因为这个集合里面本来只有文档所以语句便
取出这个唯一文档
我们尝试一下数据进行查询首先我们 insert 对数填充一定
数据
coll = db.collection("bench")
1000000.times {|i|
coll.insert({:x => 4, :j => i})
}
=> 1000000
这样我们 bench 集合插入 100 文档下面我们查询一下看看
coll.find_one({:j => 999999})
=> {"_id"=>ObjectID('4bbf93'),
"x"=>4, "j"=>999999}
电脑查询这个结果差不多 1时间因为 100文档全部查询一遍
所以这个速度不是于是我们创建索引
coll.create_index("j")
=> "j_1"
这样我们 j 这个成员创建索引查询一次试试看瞬间可以得到结果
效果非常明显
如果 find 方法代替 find_one 方法的话可以得到指向所有符合条件文档游标
(cursor )
对象
coll.find({:j => 999999})
=> <Mongo::Cursor>#
高级查询
刚才我们进行查询集合所有文档或者字段满足一定条件文档
简单情况实际查询并非如此简单关系数据库可以使用 SQL 指定条件
查询,MongoDB 当然可以例如,SQL 查询
254
----------------------- Page 264-----------------------
5.3 
Ruby 操作 MongoDB
SELECT FROM bench WHERE x = 4*
这样查询条件 Ruby 可以
coll.find({:x => 4})
=> <Mongo::Cursor>#
可以进行大小比较 SQL 查询
SELECT j FROM bench WHERE x >= 4
Ruby 可以
coll.find({:x => {"$gte" => 4}})
=> <Mongo::Cursor>#
除了等于比较以外其他比较用以“$”开头操作符表达。MongoDB
使用比较操作符 1
1 比较操作符
        
$gt {"$gt" => val}
大于 val
$lt {"$gt" => val}
小于 val
$gte {"$gte" => val}
大于等于 val
$lte {"$lte" => val}
小于等于 val
$ne {"$ne" => val}
等于 val
$in {"$in" => val}
包含 val
$nin {"$nin" => val}
包含 val
$mod {"$mod" => [n, m]}
除以n 余数m
$all {"$all" => ary}
包含 ary 所有元素
$size {"$size" => n}
数组长度 n
$exists {"$exists" => true}
存在
$exists {"$exists" => false}
存在
$not {"$not" => cond}
否定条件
正则表达式 ^foo 正则匹配
str 作为 JavaScript 进行求值
$where {"$where" => str}
this
引用文档
当然通过多个条件组合可以编写大于 2 ,小于 8”这样条件
coll.find(:j=>{"$gt"=>2,"$lt"=>8})
=> <Mongo::Cursor>#
255
----------------------- Page 265-----------------------
5 支撑大数据数据存储技术
find
方法选项
find
方法可以通过第二参数指定一些查询选项
coll.find({:x=>4},{:limit=>10})
表示查询结果取出 10 结果意思 Ruby 末尾参数 Hash 可以
花括号因此上述 find 调用可以下面形式
coll.find({:x=>4},:limit=>10)
而且 Ruby 1.9 Hash 符号(symbol )可以省略形式
coll.find({:x=>4},limit:10)
find
方法选项及其含义 2
2 find方法选项
     
:skip
整数 整数跳过指定数量结果
:limit
整数 整数取出指定数量结果
:sort
文字 字符串指定字段进行排序
数组 [字段 ,顺序 ] 格式指定排序条件指定顺序
:sort

:asc ,降序:desc。
:fields
数组指定结果文档包含字段
:hint
文字 字符串使用指定字段索引进行查询
:snapshot
真伪 布尔是否文档进行快照
:timeout TRUE TRUE
是否超时指定 :timeout 附带代码调用 find 。
find 方法选项,skip、limit、sort 可以作为 find 返回游标对象方法进行调用
因此
coll.find({:x=>4},limit:10)
可以
coll.find(:x=>4).limit(10)
sort
调用可以不在数组指定顺序而是参数指定因此
coll.find({:x=>4},sort:[:j,:desc])
游标方法形式可以
256
----------------------- Page 266-----------------------
5.3 
Ruby 操作 MongoDB
coll.find(:x=>4).sort(:j,:desc)
写法内部处理完全相同因此一种自己喜欢写法可以
原子操作
只有文档插入查询并不构成数据库完整功能我们需要进行更新删除文档
插入我们使用 save 方法保存文档赋予 _id 成员因此保存文档
_id
存在覆盖相应 _id 文档也就是说 find find_one 方法取出文档之后
文档内容进行改写然后重新 save 的话唯独不能改变 _id 成员),可以替换原来

MongoDB
支持事务机制对于其他连接同一文档进行更新行为无法做出保护
。MySQL 开始支持事务时候还是非常有用由此可见,Web 应用中的数据库系统
即便支持事务貌似不是问题
MongoDB
虽然支持事务可以通过 update 方法更新文档排除来自其他连接
干扰。update 方法文档更新操作互斥操作结果只有更新成功由于某些
出错失败状态也就是说多个连接同时同一文档进行 update 操作
操作不会发生混淆”,而是保证其中只有操作能够成功失败操作可以进行重试
结果顺序执行更新操作一样
这样更新操作不会半路中断不会留下完整状态操作称为原子操作”。
update
方法最多可以接受参数第一原始文档第二文档最后
其中选项可以省略原始文档更新之前文档这里并不需要完整文档
而是 find 方法查询相同格式即可文档更新文档这里需要
文档只要包含更新字段 Hash 即可字段存在更新其中
否则添加字段
update
方法选项 find 方法一样通过 Hash 指定。update 方法选项 3
我们 update 方法进行文档中的成员 1 这样原子操作 1 )。 1
如果调用 update 地方 save 方法代替取出文档保存时间如果
文档其他连接改写操作失效 2 为例连接几乎同时进行操作
由于取出保存文档顺序混杂因此虽然进行 1 操作实际上 x
增加 1。
257
----------------------- Page 267-----------------------
5 支撑大数据数据存储技术
3 update方法选项
  默认   
:upsert
布尔 布尔原始文档匹配文档存在创建文档
布尔原始文档匹配多个文档选项更新
:multi
布尔
所有文档更新其中文档
:safe
布尔 布尔是否真的完成更新进行确认
loop
doc = coll.find_one(:j=>0)
取出文档
orig = doc.dup
更新文档保存下来
d["x"] += 1
更新字段x
r = coll.update(orig, doc, :safe=>true)
调用 update。不可以coll.save(doc),为了
更新结果设置 :safe选项
if r[0][0]["n"] == 1
更新成功跳出循环
break
end
循环返回开头取出文档步骤开始重试
end
1 乐观并发控制
相对 1 这样使用 update 方法的话数据库中的文档
{:x=>4, :j=>0}
进行更新操作 3 一样发现文档
改写情况通过重试最终得到正确结果连接 1 连接 2
取出文档
{:x=>4,:j=>0}
取出文档
{:x=>4,:j=>0}
数据库中的文档
x 进行
{:x=>4, :j=>0}
x 进行
{:x=>5,:j=>0}
{:x=>5,:j=>0}
连接 1 连接 2
x进行update(成功)
取出文档 {:x=>5,:j=>0} x 进行 update(失败
{:x=>4,:j=>0}
取出文档
{:x=>4,:j=>0}
取出文档
x 进行
{:x=>5,:j=>0}
{:x=>5,:j=>0}
x 进行
{:x=>5,:j=>0}
x 进行
保存 x {:x=>6,:j=>0}
{:x=>5,:j=>0}
保存 x
{:x=>5,:j=>0}
x进行update(成功)
{:x=>6,:j=>0}
数据库中的文档
{:x=>5,:j=>0}
数据库中的文档
{:x=>6,:j=>0}
2 失败并发控制
3 成功并发控制
258
----------------------- Page 268-----------------------
5.3 
Ruby 操作 MongoDB
不过这样典型操作要是编写简单一些好了其实只要 update
更新文档指定稍微优化一下可以 update 语句实现某些典型原子操作
例如 1 中的操作可以下面这样
coll.update({:j=>0},
{"$inc"=>{:j=>1}})
代码意思:“找到字段 j 0 文档然后j 1”。
update
方法可以使用原子操作 4 原子操作名称是以“$”开头 3
中的“f”表示字段字段可以通过字符串或者符号形式进行指定
4 update原子操作
        
$inc
f n {"$inc" => {f => n}}
$set
f val {"$set" => {f => val}}
$unset
删除 f {"$unset" => {f => 1}}
$push
f 所指数组插入 val {"$push" => {f => val}}
$pushAll
f 所指数组插入 ary 元素 {"$pushAll" => {f => ary}}
$addToSet
f 所指数组存在 val 插入 {"$addToSet" => {f => val}}
$pop
删除 f 所指数组最后元素 {"$pop" => {f => 1}}
$pop
删除 f 所指数组第一元素 {"$pop" => {f => -1}}
$pull
f 所指数组删除所有 val {"$pull" => {f => val}}
$pullAll
f 所指数组删除 ary 元素 {"$pullAll" => {f => ary}}
ActiveRecord
Ruby 世界作为面向对象数据库访问手段有名莫过于 ActiveRecord
Ruby on Rails 关系数据库操作不是直接进行而是通过 ActiveRecord
中的记录作为对象进行操作这种对象记录进行对应称为 OR Mapper 。
其中 O 代表对象(Object ),R 代表关系(Relation ),因此就是关系对象进行映射意思
有了 ActiveRecord 帮助 Rails 程序设计可以不必关心底层关系数据库完全
面向对象方式进行编程。ActiveRecord 一种十分 OR Mapper ,并非完美无缺
其中令人不满意地方就是数据库结构信息模型定义分离由于 Rails 组件
模型 MVC (Model-View-Controller )架构中的Model 。
259
----------------------- Page 269-----------------------
5 支撑大数据数据存储技术
DRY(Don ’t Repeat Yourself )原则因此ActiveRecord 不会对数结构定义进行重复描述
数据库结构信息存在原本存在地方也就是数据库
信息重复往往造成 bug 元凶一点上来,DRY 原则非常优秀另一方面
数据操作以及对象之间关系模型进行定义这个意义上来对象结构
信息进行操作信息分开如果查看字段信息必须查看运行中的数据库
因此想要相关信息整合在一起自然不过需求
另一不满意地方 ActiveRecord 提供记录 = 对象这样抽象模型
并非总是最优简单水平,“记录 = 对象这样抽象模型实现起来容易
只要 SQL 调用得到记录装成对象可以但是对象调用变得越来越频繁
产生性能问题结果关系数据库中的记录没有成为真正意义对象
特殊情况露出抽象中的纰漏这样问题称为抽象泄漏(leaky abstraction )。
如果为了改善性能使用缓存手段的话模型逻辑变得越来越复杂另一方面
得到优化 SQL , find 方法指定非常详细选项结果无法 Ruby 编写
最后变成直接 SQL 也许已经 ActiveRecord 极限
OD Mapper
这个时候轮到 MongoDB 发挥威力由于 MongoDB 需要数据库结构因此
定义模型定义分离问题存在此外,MongoDB 没有 SQL ,原本无法进行
复杂查询因此不必担心“SQL Ruby”。当然也许大家觉得这样治标不治本
不过这个问题本来应该分开也就是说如果使用 MongoDB 的话如果应用程序能够
使用 MongoDB 实现的话), ActiveRecord 那些不满可以得到缓解
Rails 使用 MongoDB ,有一些可以ActiveRecord 互换
‰ MongoMapper
‰ Mongoid
‰ activerecord-alt-mongo-adapter
由于 MongoDB 不是关系数据库因此这些不能称之为 OR Mapper ,而是应该
Object Document Mapper (OD Mapper )
260
----------------------- Page 270-----------------------
5.3 
Ruby 操作 MongoDB
1. MongoMapper
M o n g o M a p p e r
M o n g o D B
require 'rubygems'
ActiveRecord
驱动资历 require 'mongo_mapper'
John Nunemaker 。 ,MongoDB
class Employee
2009 include MongoMapper::Document
key :first_name
key :last_name
ActiveRecord
之间兼容性 Rails many :addresses
end
程序 便 MongoMapper 当成
class Address
ActiveRecord
替代品使用各种 Rails
include MongoMapper::EmbeddedDocument
插件正常工作。 key :street
key :city
key :state
MongoMapper
中的模型定义 4 key :post_code
。MongoDB 模型进行定义 end
4  MongoMapper 进行模型定义
MongoMapper 结构定义不是必需
可以通过 key 方法字段及其类型进行声明通过这样声明可以利用
类型检查使得一些问题容易发现同时使得数据库结构能够文档刚才我们
ActiveRecord
不满意地方这里我们可以数据库结构操作同一地方进行
定义感觉非常
MongoDB 文档嵌入另一文档(JSON ),一般关系数据库

做到当然原本就是基于关系数据库 ActiveRecord 自然支持嵌入文档
MongoMapper 对于嵌入文档只要 include MongoMapper::Document 改成
MongoMapper::EmbeddedDocument ,
表示对象不是存在独立集合而是嵌入文档
2. Mongoid
Mongoid
MongoMapper 更新一些作者 Durran Jordan 。Mongoid 功能
丰富通过 ActiveRecord 类似 API 可以充分发挥 MongoDB 全部功能 Mongoid 定义
4 相同模型代码 5 一些细节有所差别大意一样不过
Mongoid
没有对于嵌入文档定义 Mongoid 其实规则凡是指定“inverse_
of”
对象关系都会自动视为嵌入一点还是相当智能
有一些关系数据库提供可以直接引用另外指定部分功能。(
261
----------------------- Page 271-----------------------
5 支撑大数据数据存储技术
MongoDB require 'rubygems'
require 'mongoid'
JavaScript class Employee
MapReduce
功能,Mongoid 通过 include Mongoid::Document
field :first_name
ActiveRecord (
以及ActiveModel )相类 field :last_name
似的 API 进行 实现 Mongoid has_many :addresses
end
设计思想之一
class Address
include Mongoid::Document
3. activerecord-alt-mongo-adapter field :street
field :city
activerecord-alt-mongo-adapter
field :state
field :post_code
SUGAWARA belongs_to :employee, :inverse_of =>
Genki 。MongoMapper
Mongoid :addresses
end
替代 ActiveRecord 使用相比
5  Mongoid 进行模型定义
,activerecord-alt-mongo-adapter
用于 ActiveRecord DB 适配器
6 )。
换句话说不是独立 class Employee < ActiveRecord::Base
include ActiveMongo::Collection
ActiveRecord has_many :addresses
end
数据库切换功能使用 MongoDB 访
适配器因此,ActiveRecord 本身 class Address < ActiveRecord::Base
include ActiveMongo::Collection
功能可以直接使用仔细的话, end
ActiveRecord
虽然通过提供相应适配 6  activerecord-alt-mongo-adapter 进行模型定义
方式实现各种数据库支持
而且只要修改配置文件可以对数系统进行切换工作方式总归还是基于 SQL
然而,MongoDB 属于 NoSQL ,当然无法 SQL 进行解释为了清楚这个适配器
如何实现 MongoDB 支持一下源代码为了能够解释 ActiveRecord
传来 SQL ,居然 Ruby 编写 SQL 语法解析对于 MongoDB 访问通过这个
SQL
语法解析完成厉害
不过 ActiveRecord 适配器形式工作并非尽善尽美范围嵌入
文档支持模型内部字段声明模型定义创建索引功能支持这些功能
数据库本来存在 ActiveRecord 本就没有考虑关系数据库进行
因此一点不能指望 ActiveRecord。即便如此通过利用 ActiveRecord ,
MongoMapper Mongoid 十分之一代码实现 MongoDB 访问可以
了不起
262
----------------------- Page 272-----------------------
5.3 
Ruby 操作 MongoDB
说到底,activerecord-alt-mongo-adapter 只是 ActiveRecord 适配器因此作为 ActiveRecord
特点之一可以通过 database.yml 开发环境 DB 正式环境 DB 之间进行自动切换
比较优点
与此同时通过这个适配器 MongoDB 全部功能而且由于需要经过
额外 SQL 解析性能方面担心
263
----------------------- Page 273-----------------------
5 支撑大数据数据存储技术
5.4 SQL
数据库反击
一种说法云计算不再 SQL 时代而是 NoSQL 时代因此依赖 SQL
简单 NoSQL 数据库到了广泛关注那么,SQL 数据库真的已经不再那么重要
SQL
数据库真的支持云计算
定义
这个多种用法不过定义非常模糊导致我们讨论容易过度发散
论点更加明确我们这里定义大规模分布式环境这个概念
当然,“并非总是代表大规模分布式环境”,数据库系统语境
一般代表现有数据库系统应对情况也就是说数据访问量两者其中
甚至全部规模已经超过单独数据库服务器所能应对程度必须依靠
服务器协同工作构成分布式环境进行应对
这样环境使用 SQL NoSQL 数据库受欢迎 SQL 这样复杂查询访问
受限取而代之多个 NoSQL 数据库自动分布服务器架构这种方式
分布式环境拥有亲和力
SQL
数据库极限
那么,SQL 数据库真的适合大规模分布式环境云计算环境真的不如
NoSQL
数据库事实上并不一定云计算环境限度利用现有 SQL 数据库技术
目前已经实用方面取得一定进展
其中基本思路对数进行分割专业领域这种数据库分割称为 Sharding
或者 Partitioning 。这种手法目的通过数据库大量记录分别存放服务器
从而避免数据库服务器瓶颈 mixi 这样社交网站(SNS )为例可以理解用户编号
偶数用户编号奇数用户分别存放不同数据库这样避免单独数据
264
----------------------- Page 274-----------------------
5.4 SQL
数据库反击
服务器集中访问从而提高处理速度第一步分割可以应用程序级别完成
例子编号偶数奇数记录分别访问不同数据库这样逻辑可以编写应用
程序
不过仔细发现其实数据库分割应用程序逻辑本质毫无关系需要
数据库层面解决问题这样逻辑混入应用程序的话说实话拙劣数据
问题应该数据库解决不是吗这样能够实现自动分割方法多种这里
我们介绍一下 MySQL 提供分割功能 Spider 。
存储引擎 Spider
Spider
ST Global 公司(Kentoku Shiba )先生开发一种存储引擎
MySQL
用于查询处理数据库引擎实际负责存储数据存储引擎相互独立对于
数据可以采用不同存储引擎可能大家听说 InnoDB、MyISAM 之类名字这些
MySQL 存储引擎
Spider
它们一样 MySQL 工作存储引擎一种不过,Spider 自身并不
实际数据存储操作而是这些操作交给其他 MySQL 服务器完成也就是说使
Spider 时候表面上看起来个数实际上可以数据自动分割存在
(sharding ),而且只要数据库保存数据同时其他服务器数据库保存
(replication )。
比起应用程序实现分割Spider 现有下列这些优点
‰
逻辑和数分离使用 Spider ,意味着应用程序看起来对数访问
通常 MySQL 访问完全一样因此应用程序需要进行任何特殊应对
‰
维护分割相关信息维护定义而且数据库分割策略可以
定义进行设置关于数据库设置集中地方一点维护角度
非常重要
刚才我们介绍利用 MySQL 存储引擎 Spider 进行自动分割手法其实实现自动分割
软件不仅只有一种 MySQL 数据库除了 Spider 之外还有 MySQL Cluster 、
SpockProxy
其他方案
SQL
数据库反驳
尽管通过 Sharding 技术数据库进行分割能够分布式环境运用 SQL 数据库
265
----------------------- Page 275-----------------------
5 支撑大数据数据存储技术
无法做到一部分 NoSQL 数据库那样能够根据需要自动增加节点实现性能扩充此外
如果 SQL 数据库实现 NoSQL 这种简单查询处理大多数情况性能
不及 NoSQL 。
虽说 SQL NoSQL 各自擅长领域不同曾经认为大规模分布式环境使
NoSQL 板上钉钉这个时候迈克尔 ·布雷(Michael Stonebraker ,1943— )
出来布雷 RDB 系统 Ingres 开发者 Ingres 商用之后开发
Ingres
后续版本 Postgres ,后者演变现在 PostgreSQL 。布雷应该称为 PostgreSQL
贡献并非仅仅如此由于 Sybase 以及 Microsoft SQL Server 中都继承开发
Ingres 代码因此毫无疑问对于 SQL 数据库整体产生巨大影响人物现在
布雷担任 MIT (麻省理工学院客座教授同时家数相关企业担任董事
布雷计算机协会 ACM ① 学术期刊《Communications of the ACM 》(ACM 通信
2010
4 刊登“SQL Databases vs NoSQL Databases”专栏② 。专栏
布雷所有技术擅长领域没有一种数据库万能前提提出
观点
‰ NoSQL
优势在于性能灵活性
‰ NoSQL
性能优于 SQL 说法并非所有情况成立
‰
认为 NoSQL 通过牺牲 SQL ACID 特性实现性能然而性能问题
SQL
ACID 无关
说实话这些内容第一反应就是:“真的?”。作为这样文章
别人灌输云计算时代 NoSQL 莫属观点实在百思不得其解
那么我们究竟根据文章决定 SQL 数据库性能客户端服务器
通信开销以及服务器事务处理开销通信开销可以通过大部分处理服务器
存储过程(Stored Procedure )一定程度得以解决
计算机协会(Association of Computer Machinary ,ACM )世界计算机业者专业组织计算
领域诺贝尔奖图灵奖就是 ACM 主办
布雷专栏参见:http://cacm.acm.org/blogs/blog-cacm/50678-the-nosql-discussion-has-nothing-to-do-with-
sql/fulltext 。(

数据库具备 4 特性首字母缩写一系列处理不会残留中间状态 Atomicity (原子)、不会产生
数据完整 Consistency (一致性)、处理中间状态进行隐藏Isolation (隔离以及完成处理进行
Durability (持久)。(
266
----------------------- Page 276-----------------------
5.4 SQL
数据库反击
对于服务器处理大致进行分类的话主要 4 瓶颈对于这些瓶颈应对
决定性能关键 4 瓶颈具体如下
日志(Logging):为了防止磁盘崩溃故障发生大多数关系数据库都会执行
数据库执行一次 日志执行一次而且为了防止日志信息丢失为了实现
ACID
中的 D ),必须保证这些数据确实磁盘这样即便由于一些问题导致数据库
可以根据日志内容恢复故障状态然而考虑磁盘速度非常
因此日志执行确定操作非常昂贵
事务(Locking):记录进行操作之前为了防止其他线程记录进行修改需要
事务形成巨大开销
内存(Latching):Latch 门闩意思这里 B 共享数据结构进行访问
需要一种排他处理方式布雷这种方式叫做 Latching 。造成开销原因
之一
缓存管理(Buffer Management):一般来说数据库数据固定长度磁盘页面
中的对于哪个数据哪个页面或者哪个面的数据存在内存需要数据库
管理开销处理
布雷认为实现高速数据库系统必须消除上述所有 4 瓶颈而且上述
并非 SQL 数据库有的这么一说好像真是这么回事
NoSQL
之所以认为速度 因为设计考虑分布式环境通过多个节点
处理分摊然而,SQL 数据库可以处理分摊多个节点此外便是 NoSQL
数据库只要涉及磁盘操作以及线程架构缓存管理难以回避上述瓶颈中的
几个
通过上面分析布雷结论无论 SQL 还是 ACID 特性不是影响云计算
环境数据库性能本质障碍原来如此
随后布雷博客① ,对于 CAP 原理相对 NoSQL 数据库策略
BASE (Basically Available, Soft-state, Eventually consistent )
阐述反对意见真是大开
眼界
:http://cacm.acm.org/blogs/blog-cacm/83396-errors-in-database-systems-eventual-consistency-and-the-cap-theorem/
fulltext (

一致性”、“分裂容忍只能同时满足其中两者据说原理已经通过数学方法
到了证明 BASE 是以满足分裂容忍目标。(
267
----------------------- Page 277-----------------------
5 支撑大数据数据存储技术
SQL
数据库 VoltDB
之前这些只是停留“SQL 数据库理论存在可能性这个阶段作为
技术大牛布雷不会仅仅满足这样结论 已经数据库业界前线活跃 40
多年绝对不是简单人物布雷美国一家叫做 VoltDB 创业公司 CTO ,
公司上述观点进行体现开发 VoltDB 数据库系统开源形式发布
惊人行动
VoltDB
版本是以 GPL 协议发布开源社区版本(Community Edition ),另一
是以订阅形式提供收费版本开源版本可以直接 VoltDB 公司官方网站获取
VoltDB
不是 PostgreSQL MySQL 那样通用数据库而是面向特定领域
(OLTP )
进行大幅度数据库系统VoltDB 主页这样介绍:“VoltDB
大规模事务处理应用程序 SQL 数据库系统。”特征包括以下几点
‰
传统 RDBMS 高出十倍性能
‰
线性扩展
‰
SQL 作为 DBMS 接口
‰ ACID
特性
‰
365 24 小时全天候工作
看起来吸引力
不过到底怎样手段实现这样特性尤其是布雷博客指出
瓶颈到底什么办法解决
首先,VoltDB 特征在于内存数据库系统也就是说数据基本上储存
在内中的由于数据存储在内缓存管理问题得以解决而且由于存在磁盘崩溃
情况需要日志这样一来瓶颈一下子解决
等等高兴电源内存中的数据消失这样数据库不是无法提供
ACID
中的持久特性其实 VoltDB 持久通过复制(replication )方式
维持。VoltDB 数据库服务器组成集群工作集群中的服务器上都
存有重复数据副本因此即便失去服务器不必担心数据丢失此外,VoltDB
定期数据文件快照功能
那么剩下瓶颈事务内存如何解决 VoltDB 数据库
268
----------------------- Page 278-----------------------
5.4 SQL
数据库反击
多个分区(partition )管理对于分区分配独立管理线程也就是说
分区操作线程因此根本需要用于实现排他处理事务内存

VoltDB
通过这样构造回避瓶颈事务(TPS )可以跑出传统RDBMS 50
成绩根据 VoltDB 公司测试数据)。怎么说呢一般磁盘数据库内存
数据库比较这种程度差距也许并不意外此外由于上述比较服务器上进
VoltDB 通过增加节点使性能线性提升也就是说如果服务器数量翻倍
性能几乎可以翻倍因此还是非常值得期待
VoltDB
架构
VoltDB
采用以内集群运用前提架构
客户端
一点传统 RDBMS 架构大相径庭
之间差异远远不仅限于没有知道 SQL
打破 RDBMS 常识想象力超高速实现
挑战布雷路上到底
RDBMS
大家应该记得上述“4 瓶颈之前
曾经通信开销问题通过存储过程
可以 一定程度得到解决”。传统数据库
磁盘
专门负责数据存储数据库服务器应用
程序通过 SQL 进行查询 1 )。对于 1 传统 RDBMS 架构
重复进行操作 通过某种手段调用服务
事先存储过程实现从而一定程度减少通信量存储过程实现方式各种
RDBMS
上都有所不同 C 语言编写载入服务器有的是 SQL 进行
编写存储过程
不过,“极端 VoltDB 可不是存储过程满足。“既然存储过程可以改善性能
那么所有事务存储过程实现好了?”于是 VoltDB 服务器调用
运行事先编写过程也就是说,VoltDB 虽然 SQL 数据库无法客户端
执行 SQL 查询实际上貌似可以只是推荐而已)。何等大刀阔斧结果
VoltDB 访问不能使用现在主流 ODBC JDBC 方式因为这些方式通过 SQL
RDBMS 功能 VoltDB 进行访问需要 Java 编写程序
269
----------------------- Page 279-----------------------
5 支撑大数据数据存储技术
也就是说 MySQL 现有 RDBMS
客户端(Java)

SQL 服务器进行访问
VoltDB
访问数据库过程本身作为 VoltDB 存储过程
存储过程存在数据库服务器中的
采用一种类似远程调用方式
过程进行调用 2 )。换句话说数据 VoltDB 集群内存
服务器客户端的界限位置有所 VoltDB 集群内存
不同。 VoltDB 集群内存
2 VoltDB 架构
VoltDB
中的编程
接下来我们看看极端设计原则,VoltDB 编程到底如何进行
VoltDB
应用程序基本上采用 3 这样结构构筑应用程序需要准备下列
部件
(1)
数据库结构定义用于定义数据库 SQL 文件内容基本上 SQL CREATE TABLE
语句此外,CREATE INDEX CREATE VIEW 可以使用
(2)
存储过程 Java 编写存储过程关于存储过程内容我们稍后详细讲解基本
负责构造 SQL 语句执行当然由于存储过程采用 Java 编写自然可以
服务器执行更加复杂逻辑
(3)
工程定义名为 project.xml XML 文件这个文件定义数据库定义文件
存储过程数据库分割基准字段信息
(4)
客户端代码客户端的源代码 Java 编写 VoltDB 访问可以使用 org.voltdb
提供功能
上述 (1) (3) 交给叫做 VoltCompiler 程序可以生成叫做目录
(catalog )
服务器程序(jar 文件)。此外,VoltCompiler 需要指定下列内容
(a)
构成集群节点数量
(b)
节点分区数量
(c)
集群首领主机名
270
----------------------- Page 280-----------------------
5.4 SQL
数据库反击
数据库结构定义
意味着 NoSQL 十分 存储过程
工程定义文件 客户端代码
常见运行时根据需要添加
动态扩展 VoltDB
VoltCompiler Java
编译器
无法实现
文档关于改变集群 目录 客户端程序
数量步骤这样
3 VoltDB 应用程序结构
数据库输出文件
修改点数重新生成目录
重新启动数据库
文件数据库载入数据
如果对数结构定义进行修改需要按照上面步骤进行操作
如果习惯 MongoDB NoSQL 数据库灵活性觉得仅仅为了修改结构定义
集群节点就要这样操作实在麻烦不过与此同时文档写道:“VoltDB 2 12
节点环境能够发挥效率 10 节点可以实现 100 QPS (查询数量),
大多数情况这样性能已经可以满足需要”。也就是说少量节点数量实现压倒性
性能因此灵活性显得那么重要某种意义这样思路 NoSQL 正好相互
对立极端
Hello VoltDB!
下面我们看看实际 VoltDB 程序 4 8 就是 VoltDB Hello World 程序具体
就是 Insert 存储过程语言名称语言对应 HelloWorld 数据库然后 Select 存储
过程读出语言名称对应 HelloWorld。
4 数据库结构定义简单 SQL CREATE TABLE HELLOWORLD (
CREATE TABLE (
创建语句没有什么难点 HELLO CHAR(15),
WORLD CHAR(15),
5
6 Java 编写存储过程定义。 DIALECT CHAR(15) NOT NULL,
PRIMARY KEY (DIALECT)
VoltDB
存储过程定义包括下列要素: );
4 数据库结构定义(helloworld.ddl)
‰
org.voltdb import
‰
@Procinfo 指定分区
271
----------------------- Page 281-----------------------
5 支撑大数据数据存储技术
‰
VoltProcedure
import org.voltdb. ;*

@ProcInfo(
‰
定义存储过程本体 run 方法 partitionInfo = "HELLOWORLD.DIALECT: 0",
singlePartition = true
必要 @ProcInfo 部分 )
一些说明。VoltDB 数据库 public class Insert extends VoltProcedure {
分区分布式配置集群
public final SQLStmt sql = new SQLStmt(
中的节点存储过程进行 "INSERT INTO HELLOWORLD VALUES (?, ?, ?);"
);
操作限于单一分区进行访问
VoltDB
, public VoltTable[] run(String hello,
String world,
如果编译指定存储过程 String language)
访问单一分区以及分区 throws VoltAbortException {
voltQueueSQL(sql, hello, world,
基准哪个字段需要 language);
voltExecuteSQL();
使用 @ProcInfo 记法。 return null;
}
project.xml
记载关于数据库 }
结构存储过程分区信息 5 Insert 存储过程(Insert.java)
import org.voltdb. ;*
@ProcInfo(
partitionInfo = "HELLOWORLD.DIALECT: 0", <?xml version="1.0"?>
singlePartition = true <project>
) <database name='database'>
<schemas>
public class Select extends VoltProcedure { <schema path='helloworld.sql' />
</schemas>
public final SQLStmt sql = new SQLStmt(
<procedures>
"SELECT HELLO, WORLD FROM HELLOWORLD " +
" WHERE DIALECT = ?;" <procedure class='Insert' />
); <procedure class='Select' />
</procedures>
public VoltTable[] run(String language) <partitions>
throws VoltAbortException { <partition table='HELLOWORLD'
voltQueueSQL(sql, language); column='DIALECT' />
return voltExecuteSQL(); </partitions>
} </database>
} </project>
6 Select 存储过程(Select.java) 7 工程定义(project.xml)
客户端代码比较简单基本上对数访问只是使用 callProcedure() 方法
project.xml
定义存储过程进行调用而已关于获取结果操作一下 8 中的示例代码
大致理解
272
----------------------- Page 282-----------------------
5.4 SQL
数据库反击
import org.voltdb. ;*
import org.voltdb.client. ;*
public class Client {
public static void main(String[] args) throws Exception {
/*
Instantiate a client and connect to the database.*
/*
org.voltdb.client.Client myApp;
myApp = ClientFactory.createClient();
myApp.createConnection("localhost", "program", "password");
/*
Load the database.*
/*
myApp.callProcedure("Insert", "Hello", "World", "English");
myApp.callProcedure("Insert", "Bonjour", "Monde", "French");
myApp.callProcedure("Insert", "Hola", "Mundo", "Spanish");
myApp.callProcedure("Insert", "Ciao", "Mondo", "Italian");
myApp.callProcedure("Insert", "こんにちわ", "
世界 ", "Japanese");
/*
Retrieve the message.*
/*
final ClientResponse response = myApp.callProcedure("Select",
"Spanish");
if (response.getStatus() != ClientResponse.SUCCESS){
System.err.println(response.getStatusString());
System.exit(-1);
}
final VoltTable results[] = response.getResults();
if (results.length != 1) {
System.out.printf("I can't say Hello in that language.");
System.exit(-1);
}
VoltTable resultTable = results[0];
VoltTableRow row = resultTable.fetchRow(0);
System.out.printf("%s, %s!¥n", row.getString("hello"),
row.getString("world"));
}
}
8 客户端代码(Client.java)
性能测试
VoltDB 官方网站(https://voltdb.com/blog/key-value-benchmarking )刊登NoSQL
代表 Cassandra 进行对比性能测试结果不过需要注意,VoltDB Cassandra 擅长
273
----------------------- Page 283-----------------------
5 支撑大数据数据存储技术
处理对象方面有所不同
例如,VoltDB 内存数据库,Cassandra 不是。Cassandra 无需定义数据库结构比较灵活
VoltDB
不是对比需要注意上述几点
VoltDB
Cassandra 以下性能测试进行对比
单纯 - :VoltDB 具备用于实现 - 存储所需充足性能因此 VoltDB 实现
- 存储 Cassandra 进行 对比测试内容 50B 12KB 进行 50 访问
更新节点、3 节点复制 3 节点复制条件对比 5 分钟处理总数
多个整数 50 32 整数作为代替单纯 - 进行对比 条件依然节点
3
节点复制 3 节点复制
多个整数批处理):依然 50 32 整数作为不同进行 10
访问更新对比条件依然节点、3 节点复制 3 节点复制
上述 3 测试结果 1 3 虽然集群构成只有 3 节点规模比较
对比范围最好情况可以跑出相当于 Cassandra 性能 16 以上成绩不过这个
测试目的为了证明使用 SQL 数据库云计算环境并不并不代表大家接下
开发应用程序数据库,VoltDB 就是最佳选择一点大家注意
1 -性能测试(5分钟事务数量
集群配置 VoltDB Cassandra   
1
节点 17000 7940 2.2
3
节点复制) 19800 17400 1.1
3
节点复制) 12600 4450 2.8
2  多个整数性能测试(5分钟事务数量
集群配置 VoltDB Cassandra   
1
节点 111000 24200 4.6
3
节点复制) 293000 38900 7.5
3
节点复制) 176000 24700 7.1
3  多个整数批处理性能测试(5分钟事务数量
集群配置 VoltDB Cassandra   
1
节点 102000 13300 7.7
3
节点复制) 286000 17200 16
3
节点复制) 172000 13000 13
274
----------------------- Page 284-----------------------
5.4 SQL
数据库反击
小结
VoltDB
一种内存数据库客户端无法直接调用 SQL ,传统 RDBMS 设计
差异然而相对展现优秀性能可以通过增加节点数量实现
NoSQL
相当线性扩展
不过由于数据基本上保存在内中的因此容纳数据总量受到服务器安装
内存容量限制此外虽然具备复制快照功能硬件故障造成数据丢失危险
感觉传统数据库一些而且数据库结构构成集群节点数量系统结构灵活
方面 NoSQL 相比依然因此必须开始对数结构进行精确设计
虽然性能掌控这个意义上来,VoltDB 可以数据库中的方程
赛车。VoltDB 刚刚诞生不久今后进行诸多改善计划据说这里提出数据
结构集群结构缺乏灵活性一点得到改善操作接口考虑支持 JSON 等等
VoltDB
可以今后值得期待一种数据库
275
----------------------- Page 285-----------------------
5 支撑大数据数据存储技术
5.5 memcached
伙伴
程序实现经常忽略程序运行时间即便采用类似实现方法有时候运行
相差大多数情况速度差异数据访问速度差异导致
程序虽然数据访问消耗时间看上去差不多实际上差别因为
数据访问需要时间数据存放位置关系例如内存中的数据硬盘数据
访问所需时间可以相差数百万
机械旋转方式工作硬盘驱动磁头旋转适当扇区需要毫秒时间
CPU 速度这些时间需要等待对内访问仅仅需要纳秒时间相比
硬盘简直停止不动一样此外位于外部内存相比位于 CPU 内部寄存器
缓存访问速度几倍
用于高速访问缓存
话虽如此内存访问速度准备硬盘同等容量内存所有数据存在

内存目前还是现实
所幸数据访问具备局部性特点也就是说操作访问数据
可以限定范围即便数据大多数操作仅仅一部分数据进行频繁
访问几乎不会其余数据
既然如此如果这些频繁访问数据复制可以高速访问地方平时那个
进行操作的话有可能性能带来大幅度改善这样能够高速访问数据存放地点”,
称为缓存(cache )。“缓存这个英文cache ,现金英文 cash 发音相同拼写
方法不同。cache 来自法语原来储藏”、“仓库意思最近 CPU 为了
不过现在服务器配置内存容量已经超过过去主流硬盘容量此外硬盘访问速度几倍
SSD (Solid State Drive ,固态硬盘已经开始普及说不定不久将来现在常识颠覆

276
----------------------- Page 286-----------------------
5.5 memcached
伙伴
高速访问数据指令配备一定高速缓存缓存本身应该泛指所有用于加速
数据访问手段
提到缓存往往包含以下含义
‰
可以高速访问
‰
改善性能目的
‰
用于临时存放数据空间可以任意丢弃出来数据
‰
数据是否存放缓存不会产生性能以外其他影响
数据库职责用来永久存储数据不可能数据任意丢弃缓存不同具有较大
随意
memcached
memcached 。memcached Danga
Interactive
公司 Brad Fitzpatrick (1980— )带领开发一种内存 - 存储软件
主要面向 Web 应用程序对数查询结果进行缓存处理
对数进行查询数据库服务器执行下列操作:①解析 SQL 语句;②访问数据
提取数据;④(根据需要数据进行更新操作考虑数据库中的数据大多存放磁盘
数据库服务器本身一定缓存机制),这样操作开销非常
另一方面近年来随着服务器内存价格下降相比购买昂贵高性能服务器
查询结果存在内存可以低成本实现高性能因此,memcached 不是真正意义
- 存储数据库而是主要着眼缓存方式改善数据访问性能
用于实现缓存功能 memcached 具备以下特征
‰
字符串对象 - 存储
‰
数据保存在内重启 memcached 服务器导致数据丢失
‰
可以数据设置有效期
‰
达到一定容量清除最少访问数据
‰
长度上限 250B ,长度上限 1MB。
memcached
能够接受命令 1
memcached
非常好用应用非常广泛根据主页介绍 2 这些服务
使用 memcached 。这些非常著名网站公司当然没有那么有名网站
277
----------------------- Page 287-----------------------
5 支撑大数据数据存储技术
memcached
使用十分广泛
1 memcached命令
     
set
设置数据
add
插入数据
replace
更新现有数据
append
之后添加数据
prepend
之前添加数据
get
获取数据
gets
获取数据唯一 ID )
cas
更新数据指定唯一 ID )
delete
删除数据
incr
数据视为数值
decr
数据视为数值减少
stats
获取统计数据
flush_all
清空数据
version
版本信息
verbosity
设置日志级别
quit
结束连接
2 使用memcached服务企业
服务名称企业名称
Bebo ,Craigslist ,Digg ,Flickr ,LiveJournal ,mixi ,Typepad ,Twitter ,Wikipedia ,Wordpress ,Yellowbot ,YouTube
示例程序
memcached
可以通过 C、C++、PHP 、Java 、Python 、Ruby 、Perl 、Erlang、Lua 语言来访
此外,memcached 通信协议简单文本形式构成使用 telnet 方式容易
进行访问开发客户端非常容易除了上述列举语言之外还有多语言提供
memcached
客户端
下面我们看看访问 memcached 客户端程序示例 1 )。提供Ruby
访问 memcached 功能这里我们叫做 memcache 正如 1 下方注释
对于同时执行查询更新操作数据进行缓存一定要注意否则一不小心容易
缓存和数之间数据匹配通过 memcache 可以容易实现事务机制下面
重新实现一下 prepend 命令 2 )。
278
----------------------- Page 288-----------------------
5.5 memcached
伙伴
require 'memcache'
#
连接memcached
MCache = Memcache.new(:server => "localhost:11211")
#
userinfo进行缓存查询
def userinfo(userid)
使用“ user:<userid>”访问缓存#
result = MCache.get("user:" + userid)
如果存在缓存返回nil#
unless result
缓存存在直接查询数据库#
result = DB.select("SELECT FROM users WHERE userid = ?", userid)*
返回结果存放缓存以便下次缓存查询#
MCache.add("user:" + userid, result)
end
result
end
#
如果userinfo进行更新需要同时更新缓存
1 Ruby 编写 memcached 客户端
def mc_prepend(key, val)
loop do
:
设置 :cas标志获取唯一 ID#
v = MCache.get(key, :cas => true)
修改 cas命令设置#
唯一 ID通过memcache_cas获取#
v = MCache.cas(key, val + v, :cas => v.memcache_cas)
设置失败返回nil (循环)#
return v unless v.nil?
end
end
2 memcached 事务示例
memcached 不满
其他大多数软件一样随着使用不断增加,memcached 到了当初从未设想
场景从而招来不满 memcached 不满主要下列这些
数据长度长度分别限制 250B 1MB 过于严格数据查询起来
时间这个角度这样数据进行缓存需求反而比较
分布服务器内存容量有限如果缓存分布服务器可以增加
数据容量然而 memcached 没有提供分布功能
279
----------------------- Page 289-----------------------
5 支撑大数据数据存储技术
持久顾名思义,memcached 只是缓存重启服务器数据丢失为了保持
缓存大小自动舍弃数据
不过对数进行访问填充缓存开销超过一定程度缓存损失就要付出
代价为了克服问题,(即便舍弃缓存最初目的便产生数据进行持久需求
最后持久一点虽然已经完全脱离缓存范畴随着应用领域扩展
还是产生各种各样类似要求
memcached
#
表示算法简单 Ruby代码
主要采取客户端方面进行努力 class DistMemcache
def add(key, value)
应对例如,Ruby memcache 计算散列#
叫做“SegmentedServer”功能, hash = hash_func(key)
根据散列选择服务器#
通过对应分别存放服务 server = @servers[hash % @servers.size]
选择服务器发送命令#
支持长度此外可以 server.add(key, value)
根据计算某种散列客户端 end
其他命令采用同样方法#
级别实现分布式数据存储支持 end
算法 3 3 memcache 客户端级别实现分布
对于持久一点客户端级别恐怕无能为力不过版本 memcached 中正
数据存储功能进行抽象从而实现数据文件数据库形式进行保存重启服务器
不会丢失
memcached
替代服务器

刚才已经,memcached 协议基于文本非常简单因此大多数 - 存储数据
可以支持 memcached 协议下面我们介绍其中数据库软件
1. memcachedb
名字字母容易搞混 Berkeley DB 保存数据 memcached
服务器。memcachedb 原本 memcached 改造开发目的为了应对 memcached 不支
数据持久问题
为了提高效率提供二进制协议。(
280
----------------------- Page 290-----------------------
5.5 memcached
伙伴
2. ROMA
参与乐天技术研究所开发 - 存储 ROMA ,支持通过 memcached 协议进行
访问。ROMA 一种重视扩展 - 存储数据库 P2P 方式节点集合构成
数据多个节点保存副本即便由于一些故障导致节点退出服务不会造成数据丢失
memcached
只是客户端实现分布相对而言,ROMA 服务器实现分布
冗余
ROMA
服务器 Ruby 进行开发采用(pluggable )架构通过Ruby 编写
插件配置服务器可以容易实现数据保存方法选择服务器命令追加
采用 Ruby 进行实现大家可能担心性能方面问题。ROMA 乐天内部各种场景
运用由于采用多个节点分担负荷实际运用没有发生性能方面问题其实
这样大规模数据中心应用发生硬件故障访问量集中导致进程终止问题几乎家常
便饭因此比起性能,ROMA 更加重视实现实际运用中的稳定性灵活性
3. Flare

Flare
GREE CTO 藤本领导开发一种 memcached 替代 - 存储特征如下
‰
使用 Tokyo Cabinet 实现数据持久
‰
数据复制服务器实现数据分区
‰
无需停止系统可以添加服务器动态重组机制
‰
节点监控故障转移(failover )
‰
支持大于 256B 大于 1MB
根据文档,GREE 使用 Flare 12 节点(6 节点、6 节点构成目前正在
超过 2000 1GB 别的数据峰值 500 ~ 1000 访问频率进行运行
条件系统基本上没有什么负担而且运行频繁更换服务器没有系统运转
产生任何问题
4. Tokyo Tyrant
Tokyo Tyrant
开发一种网络 - 存储 - 存储使
DBM
Tokyo Cabinet 先生开发相对而言,Tokyo Tyrant 提供通过网络进行访
功能。Tokyo Tyrant 支持 memcached 协议 HTTP 协议这个角度可以视为
① GREE
日本一家社交网站(http://gree.jp ),中国格力电器无关
281
----------------------- Page 291-----------------------
5 支撑大数据数据存储技术
memcached
替代服务
Tokyo Tyrant
虽然需要磁盘能够实现 memcached 同等性能
5. kumofs
kumofs
筑波大学供职美国 Treasure Date 公司开发一种 -
存储实现持久冗余故障容忍
C++ 编写 kumofs , 1 节点能够实现 memcached 几乎同等性能可以
通过增加节点进一步提高性能此外能够停止系统运行情况进行节点增加
恢复操作从而访问量增加情况作出灵活应对
一种 - 存储 Redis
上面这些例子我们可以总结一下大家 memcached 不满主要现在以下几个
方面
缺乏持久
支持分布
数据长度限制
于是为了解决这些不满出现各种各样替代服务器软件然而这里希望大家
不要误会这些不满不等于是 memcached 缺点顾名思义,memcached 原本作为数据
缓存服务器诞生因此缺乏持久支持分布以及数据长度限制作者
意图可以之所以出现这些不满试图 memcached 超出原本目的
导致结果这些不满存在表明大多数使用案例用户需要不仅仅
缓存数据库而是支持持久分布式计算真正意义 - 存储数据库
所以选择原本并不适合目的 memcached ,无非 memcached 高速吸引
结果
也就是说同时满足
高速
支持分布
持久
282
----------------------- Page 292-----------------------
5.5 memcached
伙伴
这些特性 - 存储需求真实存在而且如果不但支持单纯字符串
能够更加丰富数据结构作为使用的话大家一定非常喜欢满足上述这些
需求就是 Redis 。Redis 意大利程序员 Salvatore Sanfilippo 开发一种内存 - 存储
特征如下
内存数据库操作基本在内进行速度非常
支持永久上面提到数据库操作在内进行但是提供异步输出文件功能
发生故障最后输出文件之后变更丢失虽然并不具备严格可靠性可以避免
数据完全丢失
支持分布现行版本memcached 一样支持客户端实现分布支持。Redis Cluster
分布开发计划具备服务器复制功能
字符串之外数据结构除了字符串,Redis 支持列表(list ,字符串数组)、(set ,
包含重复数据集合)、有序(sorted set ,经过排序集合列表(hash , - 组合)。
memcached 必须强制转换字符串才能存放数据结构 Redis 可以直接存放
高速全面使用 C 语言编写 Redis 速度非常根据测试数据 Xeon 2.5GHz Linux
计算机能够处理超过 5 请求另一测试节点采用 60 线程分别产生
10000
请求调用,Redis 实现请求数量到了 memcached 两倍同样测试
memcached
成绩几乎 MySQL 10 这个角度,Redis 性能着实令人惊叹
原子由于 Redis 内部采用线程实现因此命令具备原子 incr 命令
进行干扰其他请求执行这样问题不会发生
兼容 memcached 协议:Redis 拥有自己数据结构功能比较丰富因此没有采用
memcached
协议访问 Redis 需要借助客户端 Redis 协议基于文本简单协议实际
memcached 协议相似),因此无论各种语言容易支持包括 Ruby 在内多语言
提供用于访问 Redis 功能 3 ),数量输给memcached 。
Redis
主页列出一些实际采用 Redis 企业名单 4 其中,Engine Yard
GitHub
Ruby 开发者耳熟能详公司
3 提供Redis访问语言 4 采用Redis企业
语言名称 企业名称
C ,C# ,Clojure ,Haskell ,Io ,Erlang ,Java , Boxcar ,craigslist ,Dark Curse ,Engine Yard ,GitHub ,guard
JavaScript ,Go ,Perl ,Lua ,PHP ,Python , ian ,LLOOGG ,OKNOtizie ,RubyMinds ,Superfeedr ,Vidiow
Ruby ,Scala ,Tcl iki ,Virgilio Film ,Wish Internet Consulting ,Zoombu
283
----------------------- Page 293-----------------------
5 支撑大数据数据存储技术
Redis
数据类型
之前已经,Redis 中的除了字符串以外支持列表有序散列数据结构
字符串字符串作为操作 memcached 几乎一样知道为什么,Redis
提供之前添加字符串 prepend 命令也许因为使用实在需要这个功能的话
可以通过事务实现
列表相当于字符串数组。Redis 支持数组数组这样嵌套数据结构因此数组
限于字符串
列表可以左右两端进行 PUSH (添加元素POP (取出删除元素操作
因此可以作为队列使用
相当于允许出现重复元素集合通过 Redis SADD 命令添加元素 SREM
删除元素由于没有定义元素顺序因此使用取出元素 SPOP 命令知道取出
中的哪个元素
SINTER ),
SUNION
可以得到集中属于任意元素合集)。此外可以 SINTERSTORE/
SUNIONSTORE
命令运算结果保存另一
有序:Redis 1.1 开始提供一种有序(sorted set ,Redis 术语称为 ZSET ),
这种集中元素视为数值进行排序说实话知道这样数据结构应该哪里
也许某些场合中用起来方便
列表列表就是通过查找一种 Redis 列表限定
符串
上述特征总结一下只能存放字符串 memcached 相比,Redis 一种高速
拥有丰富数据结构支持异步快照功能 - 存储不过,Redis 并非万能数据库,(
截至前来具备服务器 Sharding (分布动态重组功能没有实现非常
可靠性
数据要求可靠性也就是说万一丢掉一些数据不会产生严重后果
,Redis 就是非常理想数据库 memcached 使用实例这样领域还是
广阔
284
----------------------- Page 294-----------------------
5.5 memcached
伙伴
Redis
命令示例
Redis
命令 5 虽然形式非常类似数量远远多于 memcached 命令
5 Redis命令一览
     
连接管理
QUIT
结束连接
AUTH
简单认证可选
数据库操作
DBSIZE
当前DB 中的数量
SELECT index
数据库选择
MOVE key index
移动 index DB
FLUSHDB
删除当前 DB 中的全部
FLUSHALL
删除全部 DB 中的全部
适用所有类型命令
EXISTS key
检查 key 是否存在
DEL key
删除 key
TYPE key key
类型
KEYS pattern
列出 pattern 匹配
RANDOMKEY
随机获取
RENAME old new
重命名(new 存在舍弃
RENAMENX old new
重命名(new 存在忽略
EXPIRE key sec
设置有效期
TTL key
剩余有效期
适用字符串命令
SET key val
val (字符串赋值key
GET key
获取 key 相对
GETSET key val
val 赋值 key 获取更新原始
MGET key1 key2 ... keyN
获取多个 key 相对
SETNX key val key
存在更新 val
SETEX key time val
有效期 SET
MSET key1 value1 key2 value2... keyN valueN
多个 key val 进行更新原子操作
多个 k e y v a l 进行更新原子操作其中任何 k e y 不能
MSETNX key1 value1 key2 value2 ... keyN valueN
事先存在
INCR key
1
INCRBY key int
int
DECR key
1
DECRBY key int
int
285
----------------------- Page 295-----------------------
5 支撑大数据数据存储技术
)  
     
APPEND key val
末尾追加 val
SUBSTR key start end
获取字符串一部分
适用列表命令
RPUSH key val
列表末尾追加val
LPUSH key val
列表开头追加val
LLEN key
列表长度
LRANGE key start end
列表指定范围元素
LTRIM key start end
列表指定范围切割
LINDEX key index
指定位置元素
LSET key index val
指定位置val
列表开头删除 count val (0 删除全部负数末尾开始
LREM key count val
删除
LPOP key
获取删除列表开头元素
RPOP key
获取删除列表末尾元素
BLPOP key1 key2 ... keyN timeout
超时 LPOP
BRPOP key1 key2 ... keyN timeout
超时 RPOP
RPOPLPUSH key1 key2
key1 列表 RPOP ,然后LPUSH key2 列表
适用命令
SADD key member
添加元素
SREM key member
集中删除元素
SPOP key
集中随机删除元素
SMOVE key1 key2 member
member key1 移动 key2 原子操作
SCARD key
元素
SISMEMBER key member key
是否包含元素member
SINTER key1 key2 ... keyN
属于所有指定元素
SINTERSTORE dstkey key1 key2 ... keyN
属于所有指定元素赋值 dstkey
SUNION key1 key2 ... keyN
属于任一指定元素
SUNIONSTORE dstkey key1 key2 ... keyN
属于任一指定元素赋值 dstkey
SDIFF key1 key2 ... keyN key1
Key2 及其之后之间差异元素
SDIFFSTORE dstkey key1 key2 ... keyN
key1 Key2 及其之后之间差异元素赋值 dstkey
SMEMBERS key
集中所有元素
SRANDMEMBER key
随机获取集中元素
适用有序(ZSET )命令
ZADD key score member
添加成员已经存在更新 score )
ZREM key member
删除 member
ZINCRBY key inc member
member 得分增加inc
ZRANK key member member

ZREVRANK key member member
倒序
286
----------------------- Page 296-----------------------
5.5 memcached
伙伴
)  
     
ZRANGE key start end
获取范围元素
ZREVRANGE key start end
获取范围元素倒序
ZRANGEBYSCORE key min max
取得分为 min max 之间元素
ZCARD key
有序元素
ZSCORE key member member
得分
ZREMRANGEBYRANK key min max
获取 min max 之间元素
ZREMRANGEBYSCORE key min max
删除分为 min max 之间元素
ZINTERSTORE dstkey N key1 ... keyN
指定有序交集赋值 dstkey
ZUNIONSTORE dstkey N key1 ... keyN
指定有序合集赋值 dstkey
用于列表命令
HSET key field val
key 指定列表 field 设置 val
HGET key field
获取 key 指定列表 field
HMGET key field1 ... fieldN
获取多个 field 对应
HMSET key field1 value1 ... fieldN valueN
设置多个 field (原子操作
HINCRBY key field int
field 增加int
HEXISTS key field
判断列表是否包含 field
HDEL key field
删除 field
HLEN key
列表 field
HKEYS key
获取列表所有 field
HVALS key
获取列表所有 value
HGETALL key
获取列表所有 field value
  
SORT key
列表有序进行排序
  
MULTI/EXEC/DISCARD/WATCH/UNWATCH Redis
原子事务
Publish/Subscribe
SUBSCRIBE/UNSUBSCRIBE/PUBLISH Redis Publish/Subscribe
通信
持久控制命令
SAVE
同步保存
BGSAVE
异步保存
LASTSAVE
最后保存时间
SHUTDOWN
同步保存停止服务器
BGREWRITEAOF
替换日志文件
远程服务器控制命令
INFO
服务器信息
MONITOR
请求试用
SLAVEOF
复制设置
CONFIG Redis
设置变更
287
----------------------- Page 297-----------------------
5 支撑大数据数据存储技术
我们 1 memcached 客户端改造成了 Redis 客户端 4 )。Ruby memcache
对象自动转换字符串不过 Redis 不会进行这样自动转换因此我们需要
Marshal
转换字符串 一点 memcached 客户端区别以外其他方面基本上
相同这里我们使用字符串不过 Redis 散列表达 userinfo 可能有意思
require 'redis'
#
连接 Redis
RD = Redis.new(:host => "localhost", :port => 6379)
#
userinfo进行缓存查询
def userinfo(userid)
使用“ user:<userid>”访问缓存#
result = RD.get("user:" + userid)
如果存在缓存返回nil#
unless result
缓存存在直接查询数据库#
result = DB.select("SELECT FROM users WHERE userid = ?", userid)*
返回结果存放缓存以便下次缓存查询#
RD.set("user:" + userid, Marshal.dump(result))
else
缓存还原对象#
redis
不会进行自动字符串转换#
result = Marshal.load(result)
end
result
end
4 Ruby 编写 Redis 客户端
Redis
没有 memcached prepend 这样命令进行这样原子操作需要
。memcached Redis 事务结构方面差异。Redis 事务包裹 multi
令和 exec 命令 之间部分构成遇到 multi
命令其后面的所有命令进行参数检查, def mc_prepend(key, val)
loop do
然后记录日志随后遇到 exec 命令 RD.watch(key)
一次性原子执行所有记录下来命令 v = RD.get(key) + val
RD.multi
如果需要 prepend 这样现有 key 进行变更, RD.set(key, v)
unless RD.exec.nil?
需要事先 watch 命令指定变更 key 。 return v
end
5 通过 Redis 事务实现 prepend end
end
例子不过编写这个程序时候,Redis
5 Redis 事务示例
事务功能处于开发阶段因此无法使用 WATCH
命令
288
----------------------- Page 298-----------------------
5.5 memcached
伙伴
如果没有事务功能的话可能感觉非常别扭但是采用 Redis 场景
不一定需要事务功能因此觉得开发完善这个功能优先级并不大家可以回想一下
Web 应用程序广泛使用 MySQL 数据库曾经时间支持事务
小结
memcached
以及不满应对似乎云计算网络环境改变软件要求
结果环境变化必然加速软件进化
289
----------------------- Page 299-----------------------
5 支撑大数据数据存储技术
支撑大数据数据存储技术后记
历史上对于数据存储重大革新可以 RDB (关系数据库莫属具备
关系代数理论背景 RDB ,虽然 1970 诞生到了无数批评毫无用武之地
然而现在 RDB 几乎已经为了数据库代名词
不过进入云计算时代之后 RDB 以外其他方案开始受到越来越关注
其中 MongoDB、memcached Redis 进行介绍由于这些数据库系统
采用 SQL RDB ,因此经常称为NoSQL 。它们之所以备受关注应该因为
节点构成云计算系统数据库服务器逐渐为了整个系统瓶颈而且大多
数据库服务器形式提供 RDB ,出于各种各样原因解决瓶颈问题
当然解决问题可以采用复制多个数据库进行同步请求分配
多个数据库服务器)、分割 一定标准数据库分割多个例如编号偶数
奇数会员分别保存不同数据库技术这些技术需要客户端提供
一定支持结果场景只是需要通过获取这样简单操作并不
一定要动用 RDB 。
这样背景 出现管理数据完全存在内存中的缓存系统
memcached 。
同时由于缓存数据丢失还是需要访问数据库与其产生这样开销
自己管理文件操作于是诞生 ROMA 、Flare 软件
除此之外随着系统灵活性需求不断提高固定数据库结构(schema )已经
应对各种变化于是便出现追求数据库结构灵活性 MongoDB Redis 。另一方面
RDB
认识速度灵活性方面问题同时为了解决这些问题进行持续进化
介绍 VoltDB 正是其中一种尝试
这样对峙数据存储基础存储架构本身不断发生变化速度缓慢
硬盘(HDD )正在淘汰数据存储逐步过渡采用闪存基础固态硬盘(SSD ),同时
MRAM 、FeRAM
下一代内存开始崭露头角据说可以实现现有 DRAM 同等速度
能够闪存媲美容量而且实现永久性数据存储断电数据不会丢失)。这样
的话数据库概念有可能根本颠覆
下一代内存得到广泛运用时代曾经 Smalltalk、Lisp 那样内存空间直接保存
下来模型说不定东山再起
290
----------------------- Page 300-----------------------
5.5 memcached
伙伴
时代编程 6
291
----------------------- Page 301-----------------------
6.1
摩尔定律
关于摩尔定律已经到了多次摩尔定律美国英特尔公司 · 摩尔
(Gordon Moore )
提出集成电路中的晶体管数量大约”。下面我们
摩尔定律进行一些深入思考
实际上 1965 原始论文每年”, 10 1975 发表论文
成了”。过去 40 年中,CPU 性能大约一年半因此
以为摩尔定律内容本来 18 ”。
其实几年进行考证之前这么以为然而似乎没有证据表明 ·
摩尔提出“18 这个说法英特尔公司 David House 曾经发言提到“LSI (
规模集成电路性能 18 ”,因此 18 一说应该起源
虽然摩尔定律定律并非物理定律那样严格只是一种经验法则技术趋势
或者目标然而令人惊讶 1965 至今一定一直成立社会产生
巨大影响
几何级数增长
变为原来两倍”,就是说4 4 、6 8 、2n 2 n 次方这样增长速度
这样“n 变为 K m 次方增长称为几何级数增长
对于我们摩尔定律结果已经司空见惯也许一下子体会惊人程度
下面我们通过故事看一看这种增长速度何等令人震惊
以前地方围棋大师围棋水平天下无双于是领主:“
什么可以什么。”大师:“愿望简单只要按照棋盘格子每天
一定数量可以第一第二天每天前一天翻倍。”
什么开始?”领主,“真是明天开始。”
棋盘 19 ×19 格子也就是说主要 361 每天大师相应第一 1
293
----------------------- Page 302-----------------------
6 时代编程
第二天然后 4 、8 、 16 、32 开始大家觉得:“这么。”
几天之后情况发生变化赏赐米粒已经不下
才能这时家臣发现情况不妙
主公大事不好!”“怎么了?”“就是大师那些算了一下这个数量
不得了最后一天也就是 361 居然 2348542582773833227889480596789
337027375682548908319870707290971532209025114608443463698998384768703031934976

这么别说我们就是全世界起来不够!① ”“!”无奈领主
大师愿望
上面这个故事大家应该明白几何级数增长达到多么惊人数字
半导体业界这样增长已经持续 40 多年大量技术人员不懈努力这样奇迹变成现实
多么了不起成就
摩尔定律内涵
半导体制造使用一种类似印刷技术简单称为晶圆”(wafer )
圆形单晶薄片一层感光树脂光刻胶),然后电路影像照射晶圆其中照射
感光部分树脂保留下来其余部分露出② 。接下来露出部分进行
可以制作晶体管元件摩尔定律本质如何才能晶圆蚀刻细微电路
技术人员巨大挑战
技术人员可不是为了自我满足不断开发这种细微加工工艺电路制程缩小一半
意味着同样电路晶圆占用面积可以缩小原来 1/4 。也就是说电路设计不变
情况相同面积晶圆可以制造 4 数量集成电路材料成本可以缩减
1/4 。
缩减制程好处不仅如此构成 CPU MOS (Metal-Oxide Semiconductor ,金属氧化物
半导体晶体管制程缩减原来 1/2 可以实现 2 开关速度 1/4 电量
性质 IBM Robert Dennard ③发现因此名为 Dennard Scaling 。
实际上这个数字已经超过宇宙存在所有粒子数量。(
光刻胶一种感光之后可以显影剂溶解光刻胶一种感光之后不会显影剂溶解
光刻胶这里提到光刻胶
③ Robert Dennard (1932— )
美国电子工程师发明家 1968 发明目前广为使用 DRAM 内存
294
----------------------- Page 303-----------------------
6.1 
摩尔定律
综上所述如果制程缩减一半意味着可以同样材料制造 4 数量、2 速度
1/4
电量集成电路这些好处相当诱人,40 多年来摩尔定律能够一直成立理由正在
缩减制程带来好处如此 吸引企业投入巨额研发经费甚至出资建设
制造工厂不惜
摩尔定律结果
可以最近计算机进化普及基本上摩尔定律半导体技术发展
摩尔定律变为可能推动计算机性能提高存储媒体容量增加以及价格难以置信
下降
例如现在一般个人电脑价格超过 10 日元约合人民币 8000 ),处理性能
已经超过 30 年前超级计算机而且当时超级计算机租金就要超过每月 1 亿日元
人民币 800 ),一点上来变化可谓天翻地覆
30
年前( 1980 左右个人电脑想到就是 NEC (日本电气PC-8001 (1979
发售),现在电脑对比一下我们可以看到一些非常有趣变化 1 )。
即使考虑 30 年间物价水平变化差距可谓压倒性而且现在笔记本
电脑配备液晶显示屏大容量硬盘网络接口设备 30 年前配置 PC-8001 除了
主机之外甚至没有配备显示屏一点值得关注
1 30年间个人计算机变化
PC-8001 (NEC ) ThinkPad X201 (Lenovo )
  
价格 16 8000 日元约合人民币 13 4 820 日元约合人民币 0.8
1
3000 ) 1
CPU Z80
兼容 4MHz Intel Core i5 2.66GHz 655
存储容量
RAM 32KB 4GB 125

ROM 24KB --- ---
外部存储器 软盘 320KB 硬盘 500GB 156
① PC-8001
由于中断等待原因有效时钟频率只有 2.3MHz 左右 X201 Core i5 由于具备加速(Turbo
Boost )
功能最高时钟频率达到3.2GHz 。因此两者性能比值最高 1391 。(
295
----------------------- Page 304-----------------------
6 时代编程
摩尔律所带来可能性
不过摩尔定律所指只是集成电路晶体管数量几何级数增长趋势计算机
提高价格下降以及其他各种变化晶体管数量增长带来结果
我们思考一下通过工艺精细不断增加晶体管如何实现上述这些结果
容易理解应该就是价格单位面积晶体管数量增加同时意味着晶体管
单价几何级数下降当然工艺精细必然需要技术革新成本这种成本完全可以
量产效应抵消
工艺精细意味着制造相同设计集成电路所需成本越来越即便后面
提升性能消费晶体管数量增长绰绰有余也就是说只要工艺精细
能够得以不断推进成本方面不会存在什么问题不仅 CPU ,电脑本身就是电子元件
集合这样工艺改善带来成本下降就是上面提到 30 年来个人电脑价格方面
原动力
精细带来好处并不仅仅降低成本由于前面提到 Dennard Scaling 效应晶体管
开关速度得以实现飞跃提升相应,CPU 工作时钟频率不断提高。30 年前 CPU
工作时钟频率只有 Mhz ,现在已经 GHz 实际提高差不多 1000
由于构成 CPU 晶体管数量大幅增加通过充分利用这些晶体管提高性能 CPU
高速做出贡献现代 CPU 搭载高速方面技术例如命令处理分割
并行执行流水线处理(pipeline );直接执行机器语言而是转换更加细化内部
指令编码(micro-operation decoding );判断指令之间依赖关系没有依赖关系
指令改变执行顺序进行执行(out-of-order execution );条件分支等待条件判断结果
继续尝试执行投机执行(speculative execution )
现代 CPU 内部配备专用高速缓存通过高速缓存可以访问内存缩短等待
时间 CPU 运行速度通过外部总线连接内存访问起来非常缓慢仅仅等待
内存传输过来时间,CPU 可以执行数百指令
还好对内访问存在局部性特点也就是相同数据具有反复访问倾向因此
读取数据存放位于 CPU 内部快速存储器可以避免反复访问内存带来
开销这种方法就是高速缓存缓存英文写作 cache ,原本法语隐藏意思大概
内存中的数据贮藏起来意思
不过,CPU 内部配备高速缓存容量有限因此不少 CPU 配备作为第二梯队
296
----------------------- Page 305-----------------------
6.1 
摩尔定律
二级缓存相比能够 CPU 直接访问高速高价容量一级缓存二级缓存虽然
仍然内存访问速度),容量还有一些 CPU 甚至配备作为
梯队三级缓存如果没有高速缓存的话每次访问内存时候,CPU 必须等待能够执行
指令漫长时间
最近电脑已经逐渐普及线程(Hyper Threading )技术利用晶体管
数量提高运算性能尝试
为了提高性能
接下来我们具体看一看
流水线执行3指令需要15
那些增加晶体管到底如何 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
提高 CPU 性能。 IF DE DF EX WB
IF DE DF EX WB
IF DE DF EX WB
CPU
运行软件时候
流水线执行3指令需要7
似乎逐一执行指令其实
1 2 3 4 5 6 7 IF :
取出指令
构成 CPU 硬件电路能够 IF DE DF EX WB DE :解码
IF DE DF EX WB DF :
取出数据
同时执行多个操作指令执行 IF DE DF EX WB EX :运算
WB :

操作进行分割通过流水作业
1 CPU 流水线处理
方式缩短单独步骤处理
从而提升指令整体执行速度这种流水线处理就是一种提高性能基本技术 1 )。
典型处理步骤包括:①取出指令(fetch );②指令解码(decode );③取出运算数据 (data
fetch );④
运算;⑤输出运算结果(write-back )
我们可以看出操作划分一级处理时间相应缩短从而提升指令执行
吞吐出于这样考虑现代 CPU 中流线进一步细分例如 Pentium4
分为 31 英特尔最新 Core 架构采用 14 设计)。
不过流水线处理并非十全十美流水作业顺利执行时候没什么问题一旦
线上发生问题接连引发一连串问题流水线处理顺利进行需要
相同步伐并肩前进条件并非总能得到满足
我们 CPU 加法指令例子。x86 加法指令
ADD a b
297
----------------------- Page 306-----------------------
6 时代编程
指令意思 a b 相加结果存在 a 。a b 可以寄存器可以内存
对于 CPU 访问寄存器访问内存需要时间别的如果需要对内
访问执行取出数据时间整个流水线需要等待时钟周期这样
流水线指令执行速度带来一点提升抵消
这样流水线发生停顿问题称为气泡(bubble / pipeline stall )。产生气泡原因
需要针对不同原因采取不同对策
上述这样由于内存访问速度缓慢导致流水线停顿问题 称为数据冒险”(data
hazard ),
针对这种问题对策就是我们刚刚提到高速缓存”。高速缓存实际上消耗
一定数量晶体管用作 CPU 内部高速存储空间从而提升速度一种技术
然而高速缓存不是万能即使晶体管数量大幅增长数量不是无限因此
存在容量限制而且缓存基本工作方式读取一次数据保存下来
使下次无需重新读取”,因此对于从未读取数据依然还是花费时钟周期访问
CPU 外部内存
还有其他一些原因产生气泡例如由于 CPU 内部电路不足导致资源冒险(resource
hazard );
由于条件分支导致分支冒险(branch hazard )资源冒险可以通过增设内部电路
进行一定程度缓解
这里需要讲解一下分支冒险
CPU
内部遇到条件分支指令需要根据1指令执行结果2指令产生分支
根据之前命令执行结果判断 1 2 3 4 5 6 7 8 9 10 11 12
IF DE DF EX WB
下来执行指令位置不过 IF DE DF × EX WB
执行结果等到命令 WB ( IF DE DF EX WB
步骤完成之后才能知晓因此 2 分支冒险
线流向变得不明确 2 )。
2 首先执行 1指令并行执行 2 指令取出操作4 周期)。然而
1 指令执行完毕之前无法执行 2 指令分支 5 周期),算是一种资源冒险
1 指令执行完全结束之后可以轮到 2 指令 2 指令操作完成之后
才能确定 3 指令位于哪个位置也就是说这时能够执行 3 指令取出操作
分支 冒险那么容易解决分支预测其中一种方案分支预测利用分支指令跳转
目标偏向事先跳转目标进行猜测执行相应取出指令操作
298
----------------------- Page 307-----------------------
6.1 
摩尔定律
3 分支预测执行示意图 2 指令为止部分 2 相同为了避免
产生气泡这里分支指令进行预测开始取出指令操作预测正确整个执行
需要 9 周期分支情况相比增加 2 预测错误流水线清空从头
开始只要猜中到了猜中只是进行预测结果一样而已因此整体平均执行
速度便到了提升
最近 CPU 已经超越分支预测发展进一步投机执行技术所谓投机执行就是
条件分支跳转目标进行预测不仅仅执行取出命令操作进一步执行实际
运算操作当然条件分支预测错误需要取消刚才执行预测正确性能
提升可以进行分支预测来得更加高效
流水线一种垂直方向指令处理进行重叠提升性能技术相对水平方向
上将指令进行重叠技术称为标量(superscalar )。也就是说没有相互依赖关系前提
指令可以同时执行
例如同时执行指令标量执行情况 4 理论最好情况
6 指令需要 7 周期真的了不起加速效果 3 例子同时执行指令
只要增加执行单元可以理论极限提高 3 甚至 4 实际上最新 CPU
同时执行指令数量大约 5 左右
1 2 3 4 5 6 7
IF DE DF EX WB
根据1指令执行结果2指令产生分支
IF DE DF EX WB
1 2 3 4 5 6 7 8 9 10 11 12
IF DE DF EX WB
IF DE DF EX WB (A)
预测正确情况 IF DE DF EX WB
IF DE DF × EX WB
(B)
预测错误情况 IF DE DF EX WB
IF DE DF × × EX WB
IF DE DF EX WB
IF DE DF EX WB
3 分支预测 4 标量执行
不过事情总是没有这么简单采用标量架构 CPU ,实际能够同时执行指令数量
远远低于理想因为数据依赖关系妨碍指令同时执行例如
a = b + c
d = e + f
g = h + i
上述这样运算运算之间没有相互依赖关系极端情况即便这些运算
顺序结果不会发生任何变化这样情况能够发挥出超标量性能然而
下面这样的话
299
----------------------- Page 308-----------------------
6 时代编程
a = b + c
d = a + e
g = d + h
2 运算依赖 1 结果 3 运算依赖 2 结果这就意味着 1 运算
得出结果之前无法执行 2 运算 2 运算得出结果之前无法执行 3 运算
也就是说无法实现同时执行
于是为了增加能够同时执行指令数量可以使用执行技术这个问题本质
在于指令用于计算依赖结果指令距离正是由于相互依赖指令距离
导致 CPU 没有时间完成相应准备工作
那么我们可以改变计算结果范围改变指令执行顺序就是执行
执行英文 out of order 原本故障意思这里“order”顺序也就是命令
执行顺序排列顺序不同意思
例如
a = b + c
d = a + e (
依赖1
g = d + h (
依赖2
j = k + l
m = n + o
这样运算如果顺序改为
a = b + c
j = k + l
d = a + e (
依赖1
m = n + o
g = d + h (
依赖3
可以填满空闲执行单元顺利的话能够稀释指令之间相互依赖关系从而提高执行
效率
为了充分利用流水线带来好处出现一种叫做 RISC CPU 架构。RISC Reduced
Instruction Set Computer (
精简指令集缩写具备以下特征
‰
精简高度对称指令集
‰
指令长度完全相同例外)。
‰
传统 CPU 相比寄存器数量
‰
运算操作数只能寄存器内存中的数据需要加载寄存器
这样特征达到目的如下
300
----------------------- Page 309-----------------------
6.1 
摩尔定律
‰
通过减少指令种类使电路设计简单化高速)。
‰
通过统一指令使流水线更加容易维持
‰
根据依赖关系指令重新排序通过编译器优化实现
大约 20 之前,RISC 架构非常流行其中比较有名 MIPS SPARC 现在
RISC
虽然没有非常广泛应用智能手机使用 ARM 处理器属于 RISC 架构此外
PlayStation 3
设备采用 CELL 芯片 RISC 架构
,RISC CPU ( RISC CISC :Complex Instruction Set
Computer ,
复杂指令集指令集完全不同它们之间完全具备兼容性为了
问题过去软件资产无法充分利用不得不障碍
于是最近 x86 CPU 使用指令转换技术保持传统 CISC 指令同时试图
获得一些 RISC 优势这种技术就是外部依然使用传统 x86 指令集同时内部 x86
转换小的 RISC 指令集执行 x86 指令转换指令通过这种
方式保持兼容 x86 指令集同时根据软件实际情况可以获得依赖关系控制之外
RISC
优势即便如此填充所有标量执行单元还是十分困难
那么为什么执行单元无法有效填充原因在于数据之间存在相互依赖关系既然如此
可以没有依赖关系多个执行同时进行也就是所谓线程 (Hyper Threading )技术
线程英特尔公司专有名词技术一般名称应该叫做 SMT (Simultaneous Multi-
Threading ,
同时线程),不过为了简便起见这里统一使用线程
所谓线程就是通过同时处理多个取出执行指令控制流程从而没有相互依赖
运算同时运算通过手段可以提高超标利用效率实际上为了同时
处理多个控制流程线程),需要增加相应寄存器资源
线程空闲运算一种有效利用不是可以线程数量比例高性能
根据英特尔公司发布数据线程最多提升 30% 左右性能不过为了实现 30%
提升晶体管数量仅仅增加 5%。 5% 晶体管增加换取 30% 性能提升应该
笔划交易
除了上述这些以外还有其他一些提高性能方法例如一块片中封装多个核心
(multi-core )技术最近操作系统进程早已司空见惯运用空间愈发
广阔分为形式包含多个相同种类核心同构(homogeneous multi-core ),
包含多个不同种类核心异构(heterogeneous multi-core )。异构除了通常
CPU
以外可以包含 GPU (Graphic Processing Unit ,图像处理单元视频编码核心
301
----------------------- Page 310-----------------------
6 时代编程
包含甚至数百核心芯片正在研究称为(many-core )。
摩尔定律极限
过去 40 一直不断改变世界摩尔定律今后是否能够继续有效下去目前
并不乐观出于几个理由芯片集成提高似乎已经接近极限
第一极限就是导线宽度随着半导体制造技术进步 2010 小的制程已经
达到 32nm (纳米,1 纳米 1 10 亿之一)。刚才已经集成电路采用一种类似
技术制造用光照射模板按照模板图案感光半导体形成电路问题
电路已经变得过于精密甚至感光光源波长目前采用感光光源紫外激光
紫外激光波长 96.5nm 。
森林阳光透过茂密树叶地面影子变得模糊无法分辨单独
同样图案波长小时发生模糊无法清晰感光情况为了能够
波长小的电路人们采用各种各样方法例如透镜晶圆之间填充
缩短波长这个极限迟早到来下一步恐怕使用波长紫外线 X 射线
波长的话透镜无法使用① ,处理起来十分困难或许可以反射镜替代透镜
曝光机构变得非常庞大成本上升
其次即便这样真的能够形成更加细微电路发生另外一些问题电路变得非常
精细发生一些超出经典物理进入量子物理范畴现象其中例子就是穿效应”。
关于穿效应详细知识这里省略因为自己明白),简单便是电流
无法通过绝缘体微观尺度少量电子能够穿透产生微弱电流这样电流
称为渗漏电流现代 CPU 一半以上电力消耗渗漏电流
精密电路产生发热问题电流电路中流产生热量随着电路精密化
密度单位面积产生热量随之上升现代 CPU 密度已经烤炉差不多
如果不用风扇进行冷却恐怕真的可以用来煎蛋上面提到渗漏电流转化热量
因此提升密度因素之一
假设电路精密化保持现在一样速度恐怕不久将来看到这样情形——
开关一瞬间整个电路蒸发如果没有适当冷却措施的话)。
① X
射线使用透镜聚焦
302
----------------------- Page 311-----------------------
6.1 
摩尔定律
最后难题就是需求饱和最近电脑 CPU 性能已经显得有些驻足
CPU
指标最为应该就是尽管 CPU 单位频率性能有所差异过去一直
快速增长 CPU 几年开始 2GHz 水平止步便是高端 CPU 如此
过去 Pentium 4 时代能够见到 4GHz 级别产品今天已经销声匿迹
因为收发邮件浏览网页撰写文稿这些一般大众别的应用需要电脑性能
低端 CPU 完全可以满足竞争降温现状不无关系
进一步过去人们一直习惯认为 CPU 性能决定现在技术
影响已经不是决定性能唯一因素成为竞争必然日趋下降
原因实际上著称 Pentium 4 ,单位频率性能不怎么可以竞争
扭曲一代 CPU
上面介绍这些摩尔律所构成障碍依靠各种技术革新克服它们应该并非
可能只是这样做伴随着一定成本技术革新角度如果制造昂贵 CPU
出去这样环境理想当然总有一些领域 3D 图形视频编码物理计算
即便强大 CPU 不够但是这样领域毕竟有限每年不断高涨技术革新成本到底
如何筹措还是应该放弃技术革新竞争退出近年来受到全球经济形势低迷影响
制造商面临艰难抉择
超越极限
正如之前摩尔定律已经接近极限不争事实退即使集成电路
精密化真的能够现有速度一直演进下去总有晶体管变得原子
不过距离终极极限尚且还有一定余地现在我们面临课题解决起来的确
难度没有到达无法克服地步
首先关于导线宽度问题运用紫外线 X 射线 工艺已经处于研发阶段由于这些
波长光源难以掌控因此装置变得成本变得反过来说我们已经
知道这样做法行得通剩下事情只要花钱能够解决
比较难以解决渗漏电流及其伴随发热问题随着半导体工艺技术改善对于
降低渗漏电流提出多种方案例如通过晶体形成二氧化硅绝缘降低渗漏
电流 SOI (Silicon On Insulator )技术此外采用以外材料制造集成电路技术
正在研究之中距离实用比较遥远
303
----------------------- Page 312-----------------------
6 时代编程
现阶段根本解决渗漏电流问题困难但是通过切断空闲核心
电路供电抑制电量抑制发热),以及关闭空闲核心提升剩余核心工作(Hyper
Boost )
技术目前已经实用
不再免费午餐
上面介绍想必大家已经摩尔定律以及随之不断增加晶体管能够造就何等
CPU 有了大致了解现代 CPU 通过大量晶体管实现高速技术随处可见
然而与此同时我们印象中的 CPU 执行模型实际 CPU 内部处理已经大相径庭
条件分支导致流水线气泡以及为了克服内存延迟使用高速缓存 8086 时代
难以想象
而且什么不用考虑随着时间推移 CPU 自然变得越来越这样趋势快要
接近极限长期以来软件开发者一直受到硬件进步恩惠即便进行任何优化随着
更新换代同样价格能够性能越来越不过现在即便计算
有时并不带来直接性能提升提升性能必须积极运用以及 CPU
特性
最近,GPGPU (General Purpose GPU ,即将 GPU 用于图形处理之外通用编程到了
关注由于 GPU 传统 CPU 计算模型有着本质区别因此需要采用专门编程
技术
即便什么,CPU 变得越来越时代结束今后为了活用硬件软件
必须付出努力——这样情况称为免费午餐终结”。
未来软件开发如果不能了解 CPU 趋势无法提高性能计算设备必然
需要计算模型这样时代已经到来
304
----------------------- Page 313-----------------------

6.2 UNIX
管道
诞生 20 世纪 60 年代后半 UNIX ,之前操作系统相比具有一些独到特点
之一就是文件结构 UNIX 之前大多数操作系统中的文件结构文件如果
COBOL 的话解释起来容易一些所谓结构文件就是拥有结构记录罗列 1 )。
UNIX 设计方针重视简洁因此 UNIX 抛弃文件本身赋予结构做法
文件定义单纯节流对于这些节流应当如何解释交给应用程序负责
文件内容文本还是二进制没有任何区别
结构文件 例如 1 平面文件(flat file ),采用
姓名 本行 记录记录成员之间逗号进行分隔 CSV (Comma
地址 松江 Separated Values )格式表现数据不是UNIX
电话号码 0852-28- ××××
员工编号 7 CSV 这种文件格式特别规定只是相应应用程序
基本工资 200000 平面文件存放 CSV 数据进行解释而已
姓名
地址 京都 平面文件(CSV)
电话号码 03-3855- ××××
姓名地址电话号码员工编号基本工资
员工编号 33
本行 , ,0852-28-XXXX,0007,200000
基本工资 180000
, 京都 ,03-3855-XXXX,0033,180000
: : …
1 结构文件平面文件
UNIX
另一独到就是 Shell 。Shell UNIX 用来用户进行交互界面同时
能够命令理化一种语言
UNIX 之前 操作系统类似命令管理语言 JCL (Job Control Language )。
JCL 相比,Shell 作为编程语言功能更加丰富可以多个命令进行灵活组合如果
重复执行同样操作只要操作过程记录文件能够容易作为程序执行
这样执行记录生成程序称为脚本(script ),之后脚本语言(script language )
名称
这里管道中的流水线来自同一英文单词 pipeline ,它们本质上含义相同
不同领域习惯不同
305
----------------------- Page 314-----------------------
6 时代编程
UNIX 至今依然存在 script 这个命令这个命令功能用户 Shell 中的输入内容
记录文件根据记录内容可以编写脚本程序。script 原本剧本意思
命令行输入一种即兴记录也许叫做 improvisation (即兴表演更加合适
最后一点就是串流管道(stream pipeline )。UNIX 进程具有标准输入标准输出还有
错误输出默认输入输出目标 Shell 启动命令可以这些输入输出目标进行连接
替换通过这样方式可以命令输出作为另一命令输入输出进一步
作为另一命令输入也就是实现命令串联”。
现代我们看来特征已经司空见惯可以想象 UNIX 诞生
这些特征可是相当创新
管道编程
下面我们看一看运用串流管道实际程序 2 经常用作 MapReduce ①例题
统计文件单词个数程序
通过这个程序读取 Ruby README 文件输出 3 这样结果
tr -c '[:alnum:]' '¥n' | grep -v '^[0-9] $' | sort | uniq -c | sort -r*
2 单词计数程序
Shell
“|”连接命令标准输出标准输入连接起来形成管道这个
以下 5 命令组成管道
1. tr -c '[:alnum:]' '\n'
2. grep -v '^[0-9] $'*
3. sort
4. uniq -c
5. sort -r
下面我们具体讲解一下命令功能
“tr”
translate 缩写功能输入数据进行字符替换。tr 第一参数指定
字符集合这里 [:alnum:] 表示字母数字意思第二参数指定字符进行替换。“-c
(complement )”
选项意思反转匹配整体命令功能就是字母数字以外
字符换成换行”。
一种通过分布式并行处理庞大数据库进行高速访问手法分为“Map”“Reduce”步骤进行处理。(

306
----------------------- Page 315-----------------------
6.2 UNIX
管道
grep
命令用来搜索模板匹配这里模板通过正则表达式指定
^[0-9] $*
这里,“^”表示匹配,“$”表示匹配,“[0-9]*”
33 ruby
匹配“0 多个数字组成字符串”。结果模板匹配 23 the
19 to
只有数字或者”。 16 prefix
16 DESTDIR
-v (revert )
选项表示反转匹配也就是显示匹配因此, 13 and
13 Ruby
grep 命令执行结果删除或者只有数字”。 11 lib
11 is
之前 tr 命令 已经字母数字之外字符全部替换成了 11 TEENY
11 MINOR
也就是说符号空格全部转换成了只有换行 11 MAJOR
)。计数没有意义因此需要忽略这些。 7 of
6 you
此外只有数字不能算是单词因此需要忽略。 6 org
6 lang
接下来 sort 进行重新排序命令命令之前, 6 in
6 be
数据流已经转换单词排列形式通过 sort 命令 5 not

原文出现单词按照字母顺序进行排序排序操作
没什么接下来我们需要 uniq 命令去掉重复因此 1 Author
1 Aug
必须事先输入数据流进行排序。 1 Advanced
3 单词计数结果节选
uniq
unique 缩写命令可以排序文件去掉
。-c (count )选项表示去掉重复同时显示重复这里我们输入文件
单词形式因此统计排序单词序列重复相当于是统计
数量。uniq 命令单词计数本质部分
最后我们 sort -r 命令输出信息进行整形。uniq 命令执行完毕之后完成统计
单词数量任务人类角度单词出现数量降序排列自然
我们执行一次 sort 命令
我们希望查看统计结果出现数量最多单词可以认为比较重要单词前面
因此我们 sort 命令加上 -r (reverse )选项这个选项代表降序排列意思这个命令
副作用就是出现数量相同单词按照字母逆序排列一点大家多多

一种特殊字符描述字符串匹配模板手法。(
307
----------------------- Page 316-----------------------
6 时代编程
单词出现数量降序排列同时出现相同单词字母顺序排列实现起来
出乎意料麻烦这里各位读者思考其实 Ruby Awk 可以比较
解决这个问题
上面这样完成一个个简单任务命令组合起来形成管道可以完成各种各样工作
就是 UNIX 范儿管道编程
时代管道
UNIX 诞生 20 世纪 60 年代 CPU 存在因此管道原本设计并非
前提然而不知偶然还是必然管道对于运用却是非常有效
下面我们看看环境管道执行何等高效
首先我们思考一下非常原始任务操作系统例如 MS-DOS 。原始”,其实
MS-DOS
相比 UNIX 算是非常年轻这里我们忽略一点 MS-DOS 同时
只能进程工作因此管道通过临时文件实现例如执行下列管道命令
command-a | command-b
MS-DOS (
准确应该相当于Shell
command-a
临时文件
command.com )
生成临时文件
“command-a”输出结果文件临时文件 command-b
command-a
执行结束之后
4 任务操作系统管道
文件作为输入执行“command-b”。
由于 MS-DOS 任务操作系统每次只能进行处理当然无法进行运用
4 )。
接下来我们思考一下环境任务操作系统这样环境管道命令
执行由于只有核心因此无法做到完全同时进行刚才一样执行下列命令
command-a | command-b
command-a command-b 同时启动
,command-b
command-b
读取数据发出系统调用如果暂时没有即可读取数据操作系统
数据准备之前暂停 command-b 进程使休眠
308
----------------------- Page 317-----------------------
6.2 UNIX
管道
另一方面,command-a 继续执行结果输出地方这样一来 command-b 有了
读取数据,command-b 进程唤醒恢复执行
这样数据输出接力棒形式进行运作多个进程交替工作就是任务环境
执行方式 5 )。
任务相比任务环境优势在于没有无谓文件输入输出操作从而削减
开销
而且由于多个进程依次执行得出结果立即通过管道传递因此获取结果
比较一些
不过任务环境进程切换需要一定开销总体执行时间未必缩短
接下来终于讲到环境管道简单起见这里我们假设 command-a
command-b
分别分配不同核心这样情况管道执行 6
command-a
初始化 处理 输出 处理 输出 处理 输出
command-b
初始化 输入 处理 输入 处理 输入 处理
5 任务操作系统管道
command-a
初始化 处理 输出 处理 输出 处理 输出
command-b
初始化 输入 处理 输入 处理 输入 处理
6 任务操作系统管道
我们可以看出 5 相比同时执行部分增多非常粗略一下 4 需要
11
完成处理这里需要 8 完成不过我们投入核心理想状态应该
缩短一半这样理想状态实现
假设操作系统足够聪明前提只要增加管道级数使能够重叠部分相应增加
即便特意多个核心配置只要自然编写程序形成管道操作系统自动利用多个
提高处理能力之所以串流管道非常适合一种编程模型原因正是在于
xargs——
一种运用核心方式
大家知道 xargs 这个命令? xargs 用于标准输入转换命令行参数命令
309
----------------------- Page 318-----------------------
6 时代编程
例如当前目录搜索所有文件“~”结尾文件需要执行 find 命令
# find . -name '*~ '
表示换行
这样符合条件文件标准输出列出
那么如果这些文件全部删除的话怎么这时轮到 xargs 命令出场
# find . -name '*~ '|xargs rm
这样一来传递 xargs 标准输入文件名列作为命令行参数传递 rm 命令于是
除了符合条件所有文件
还有有人实际碰到问题就是命令行参数数量上限如果传递
参数过多命令执行失败。xargs 考虑一点参数过多分成命令分别执行
上面内容没什么关系不过 xargs 提供用于命令行参数“-P”。
7 用于当前目录压缩扩展名不是 .gz 文件全部进行压缩
管道命令
# find . \! -name .gz -type f -print0 | xargs -null -P 4 -r gzip -9 *
7 文件压缩管道命令
首先 find 命令含义如下
‰ “.”
表示当前目录
‰ “\! -name *.gz”
表示文件 .gz 结尾
‰ “-type f”
表示一般文件不是目录特殊文件
‰ “-print0”
表示符合上述条件文件打印标准输出为了应对包含空格文件
采用 null 作为分隔
这样我们到了当前目录压缩文件列表”。得到列表之后,xargs 命令执行
xargs
命令中的“-P”选项表示同时启动指定数量进程这里我们设定同时执行 4 进程。“-r”
选项表示输入启动命令 存在符合条件文件表示不用进行压缩因此
我们这里使用“-r”选项
为了应付空格,find 命令使用“-print0”选项相应必须同时使用“-null”选项
这样操作实现将要压缩对象文件作为参数递给“gzip -9”命令执行
310
----------------------- Page 319-----------------------
6.2 UNIX
管道
gzip
命令“-9”选项表示使用压缩花费时间)。
我们知道文件压缩比单纯输入输出更加耗时而且多个文件压缩操作之间
相互依赖关系这些操作相互独立进行对于这样操作如果能够分配多个进程
同时进行应该适合环境工作方式
环境是否 xargs 命令使用“-P”选项直接影响处理需要时间由于 gzip
命令输入输出操作需要一定处理时间因此 -P 设定进程应该大于实际核心
双核电脑进行测试核心设定 4 进程执行可以获得最高性能
不过测试文件数量即便使用 -P 选项只能启动进程
无法充分利用这种情况 xargs 命令使用 -n 选项设定 gzip 一次性处理文件数量
也许主意
例如如果使用“-n 10”选项可以 10 文件启动 gzip 进程
启动 4 进程进行并行压缩处理速度可以提高大约 40% 。理想状态核心
可以得到 100% 性能提升因此 40% 成绩预想当然说明实际
一部分输入输出开销无法通过增加核心数量弥补
注意瓶颈
这里需要注意瓶颈到底发生哪里
环境任务分配多个 CPU 提高单位时间处理能力一种手段也就是说只有
CPU 能力成为处理瓶颈手段才能有效改善性能
然而一般计算机尽管搭载多个 CPU ,其他设备内存磁盘网络
共享处理瓶颈存在 CPU 之外这些地方即便投入多个核心丝毫无法
改善性能
这种情况我们需要不仅多个 CPU ,而是计算机组成分布式计算环境
分布式计算相当重要技术我们这里不再过多赘述
定律
定律估算通过并行能够获得多少性能提升经验法则吉恩 ·
311
----------------------- Page 320-----------------------
6 时代编程
(Gene Amdahl ,1922 ~)提出内容
通过并行计算获得系统性能提升效果 随着无法并行部分产生
饱和
正如刚才 xargs 示例遇到便是计算机一般只有输入输出控制
这个部分无法获得并行计算带来效果容易成为瓶颈
而且数据之间存在相互依赖关系依赖数据准备之前即便有空核心
无法开始工作成为瓶颈
综上所述大多数处理具备只要增加核心能够提高速度良好性质
一点 CPU 内部实现流水线艰辛似乎存在一定相似性
根据定律并行之后速度提升比例可以通过 8 公式估算假设 N
速度提升最多只能达到
1  / (1 - P)
例如即便 P 90%
1
理想情况无论如何提高并行 速度提升比例 = 1−P + P / N
( )
程度整体最多能够获得性能 P=并行部分占比对于基准运算时间而言并行处理
无法超过基准 10 部分比例
N=
并行处理器数量
,“(1 - P)”代表无法并行 8 并行速度提升比例公式
部分成为瓶颈使得并行效果
极限
编译
我们这些工程师电脑消耗 CPU 工作恐怕就是编译当然编译伴随
输入输出操作预处理语法解析优化代码生成操作对于 CPU 开销相当
编译文件首先需要 C 语言文件(*.c )进行预处理(cpp )。cpp 进行文件(*.h )
加载(#include )、定义(#define )、展开操作
cpp
运行结果编译器主体(ccl )。ccl 进行语句语法解析代码优化输出
汇编文件(*.s )。随后汇编汇编文件转换对象文件(*.o ),有些编译器可以通过
汇编直接输出对象文件
312
----------------------- Page 321-----------------------
6.2 UNIX
管道
C 语言文件完成编译生成相应对象文件之后可以启动连接器(ld )
生成最终可执行文件连接器对象文件各种文件静态链接 *.a 动态链接库
*.so )
进行连接某些情况进行
一些优化),输出最终可执行文件 预处理 编译 汇编 链接
*.c (cpp) (cc1) (as)
9 )。
*.h *.o (ld)
可执行文件
UNIX
“make”工具提供
正好可以用于选项——“-j *.a,*.so
(jobs )”,
通过这个选项可以设定同时 9 C 语言编译流程
执行进程数量例如
# make -j4
表示 4 线程进行并行编译过去经验,-j 设置应该大于实际核心数量

ccache
我们放下的话别的叫做 ccache 工具可以有效提高编译速度
ccache
通过编译结果进行缓存减少再次编译工作量从而提高编译速度
使用方法简单编译在编名称前面加上 ccache 即可例如
# CC='ccache gcc' make -j4
这样可以再次编译所需时间大幅缩短每次指定的话比较麻烦可以开始
Makefile
源代码或者其所依赖文件发生修改,make 重新执行编译不过文件
实际变更部分无关 ccache 函数单位编译结果存在
“.ccache”目录然后实际执行编译之前过去编译结果进行比较如果
代码相应部分没有发生修改直接使用过去编译结果
CPU 缓存高速重要手段改善编译速度场景可以应用缓存
技术这样类似手段出现各种不同场景的确有意思事情
distcc
还有其他一些改善编译速度方法例如 distcc 就是一种利用计算机改善编译速度
工具
313
----------------------- Page 322-----------------------
6 时代编程
ccache 一样只要在编名称前面加上 distcc 可以改善编译性能不过在此之前
需要配置好用哪些计算机执行编译操作下列配置文件
# /.distcc/hosts ~
填写用于执行编译主机名多个主机名之间逗号分隔)。
当然不是随便主机可以基本上用于执行编译主机应该启动
distccd
服务主机或者可以通过 ssh 登录主机启动 distccd 服务主机直接填写
主机名 ssh 登录 主机前面加上“@”。登录用户名不同需要 @
用户名
通过 ssh 执行提高安全性(distccd 没有认证机制),由于加密带来开销编译
下降 25% 左右因此用户需要性能安全性易用性之间做出选择
准备妥当之后执行
# CC='distcc gcc' make -j4
可以实现分布式编译
distcc
伟大在于虽然分布式编译无需拥有所有文件文件完整
只要同一 CPU 安装编译器能够运行ssh 主机可以容易实现
编译之所以能够实现一点秘密在于处理器连接器本地执行发送
主机已经完成预处理文件
编译性能测试
那么通过使用上述这些手段到底能够编译性能
1 编译性能测试
带来多大改善我们实际测试一下
编译条件 编译时间
gcc -j1 18.464
1 显示运用各种手段测试结果 编译
gcc -j2 10.611
对象最新 Ruby 解释器用于执行编译
gcc -j4 10.823
古老——ThinkPad X61 Core2 duo 2.2GHz (双核)。 gcc -j8 11.006
distcc
分布式编译使用 Quad-Core AMD Opteron ccache -j1 20.874
2.4GHz (
计算机。 ccache -j1 (2) 0.454
distcc -j2 11.649
使用经过任何优化 gcc 进行编译整个编译 distcc -j4 7.138
需要 18.5 使用 make -j 选项启动多个进程, distcc -j8 7.548
314
----------------------- Page 323-----------------------
6.2 UNIX
管道
由于充分利用核心使得速度提高 40% 以上
ccache
首次执行通常情况一点 由于编译结果缓存起来删除对象文件
之后完全相同条件再次编译由于完全需要执行实际编译操作需要取出缓存
内容可以完成处理因此编译速度惊人
distcc
测试主机 make -j2 情况由于 ssh 开销较大因此本地
执行相比性能改善不大如果设置进程数量执行时间可以大大缩短
小结
定律指出并行存在极限因此无法解决所有问题但是大家
应该能够看出只要配合适当编程技巧还是能够比较容易获得效果可以
将来还是前途
315
----------------------- Page 324-----------------------
6 时代编程
6.3
阻塞 I/O
需要处理大量连接服务器如果使用线程的话内存负荷线程切换开销都会
非常巨大因此监听输入进来事件进行应对处理采用线程实现更加高效
这样通过事件应对处理方式工作软件架构称为事件驱动模型(event driven
model )。
这种模型虽然可以提高效率缺点采用线程进行处理情况事件
过程由于某些原因需要进行等待程序整体停止运行意味着即便产生
事件无法进行对了
这样处理发生停滞情况称为阻塞阻塞多半等待输入输出时候发生对于
驱动程序阻塞应当极力避免
何为阻塞 I/O
由于大部分输入输出操作免不了遇到阻塞因此输入输出需要尤其注意输入
操作速度不快因此需要进行缓冲数据到达缓冲读取操作需要缓冲中将
数据复制出来可以
缓冲机制情况产生等待一种缓冲需要等待数据到达缓冲
读取);一种缓冲需要等待缓冲腾出空间入时)( 1 )。
相当于程序停止工作阻塞状态
尤其是输入读取如果数据到达试图执行读取操作一直等待数据到达
这样肯定发生阻塞
之下输出由于磁盘网络传输因素有可能发生阻塞发生概率
并不而且即便发生阻塞等待时间相对因此不必过于在意
实现阻塞读取操作下列方法
316
----------------------- Page 325-----------------------
6.3 
阻塞 I/O
‰
使用 read(2) 方法
‰
使用 read(2)+select 方法
‰
使用 read(2)+O_NONBLOCK 标志方法
‰
使用 aio_read 方法
‰
使用信号驱动 I/O 方法
这些方法缺点我们逐一讲解一下
线程读取数据缓冲情况
缓冲
线程 数据 磁盘
等待数据到达
线程数据缓冲情况
缓冲
线程 数据 数据 数据 数据 磁盘
等待缓冲
1 输入输出发生阻塞原因
使用 read(2) 方法
首先我们确定示例程序结构这里, int
我们程序实际负责读取处理回调部分。 callback(int fd, void data)*
{
....
我们回调函数名为 callback ,参数用于 / 返回成功 1 /* *
/
到达EOF0 /* *
读取文件描述(int fd )注册回调函数指定
/
失败 -1 /* *
指针(void *data )(2 )。关于输出我们设置 }
output 函数。 void
output(int fd, const char p, int *
程序需要事件循环使用选择 len)
{
读写文件描述“select 系统调用“epoll ”, ....
文件描述进行监视数据到达文件描述 }
调用相应回调函数 2 回调函数输出函数
317
----------------------- Page 326-----------------------
6 时代编程
我们看看使用 read 系统调用 void
实现方法对了所谓 read(2) , UNIX callback(int fd, void data)*
{
广泛使用一种记法代表手册显示命令 char buf[BUFSIZ];
man
2 中的 read”意思由于 2 int n;
系统调用因此可以认为 read(2) 相当于 n = read(fd, buf, BUFSIZ);
if (n < 0) return -1; /
失败 /* *
“read
系统调用缩写
if (n == 0) return 0; / EOF /* *
output(fd, buf, n); /
/* *
使 read(2) 3 return 1; / 成功 /* *
}

3  read(2) 实现输入操作(ver.1)
程序非常简单这个回调函数调用
显然输入数据已经到达因此只要
void
read 系统调用积累输入缓冲中的 callback(int fd, void data)
*
数据复制 buf 即可输入数据到达, {
char buf[BUFSIZ];
read
系统调用不会发生阻塞。 int n;
read
系统调用功能:①失败返回 for (;;) { / *
n = read(fd, buf, BUFSIZ);
负数;②到达 EOF 0 ;③读取成功 if (n < 0) return -1; / 失败 /* *
返回读取数据长度只要明白这些 if (n == 0) return 0; / EOF /* *
output(fd, buf, n); /
*
容易理解 2 程序为了小菜一碟。 */
if (n < BUFSIZ) break; /
读取完毕退出 *
不过这样简单实现版本必然 */
}
问题发现这个回调函数 return 1; / 成功 /* *
}
前提 数据长度小于
4  read(2) 实现输入操作(ver.2)
BUFSIZ(C
语言标准 IO 定义常量
貌似 8192 )。
但是通信使用数据长度一般不是固定某些情况需要读取数据长度
超过 BUFSIZ 。于是能够支持读取长度超过 BUFSIZ 数据版本 4
版本 2 读取数据长度小于 BUFSIZ 也就是输入缓冲中的数据已经
读取出来时候程序结束读取数据长度等于 BUFSIZ 表示缓冲可能
残留数据因而通过反复循环直到读取完毕为止
问题解决没有事情那么简单输入数据长度正好等于 BUFSIZ
这个程序发生阻塞我们避免阻塞对于回调函数非常重要因此这个程序
无法实际使用我们需要进行一些改进
318
----------------------- Page 327-----------------------
6.3 
阻塞 I/O
边沿触发触发
好了接下来宣布重要我们刚才 3 程序只能支持读取长度小于
BUFSIZ
数据其实只要读取数据直接输出还是可以正常工作而且不会发生阻塞
不过实现一点负责事件监视部分需要满足一定条件

方法 边沿触发(edge 状态
trigger )
触发(level trigger )。
时间
原本机械控制领域
中的边沿触发状态变化
瞬间发出通知 触发 边沿触发 ○ × ×
状态发生变化整个过程中都 触发 ○ ○ ×
发出通知 5 )。 5 边沿触发触发
select
系统调用属于触发,epoll 默认触发 epoll 可以通过设置
边沿触发
具体 epoll_event 结构 events 字段通过 EPOLLET 标志进行设置
3 程序阻塞状态工作事件监视必须采用触发方式也就是说
调用回调函数执行输入操作之后如果读取缓冲还有残留数据触发方式
再次调用回调函数进行读取操作
那么采用触发足够边沿触发存在还有什么意义由于边沿触发
到达瞬间产生事件因此总体事件发生次数比较意味着回调函数
次数比较可以提高效率
使用 read(2) + select 方法
刚才已经 3 版本程序输入缓冲积累数据全部读取出来输入
缓冲调用 read 系统调用发生阻塞为了避免这个问题需要调用 read 之前
输入缓冲是否
下面我们创建 checking_read 函数调用 read 系统调用然后通过 select 系统
调用检查输入缓冲是否数据 6 )。为了判断是否剩余数据,checking_read read
319
----------------------- Page 328-----------------------
6 时代编程
参数调用 checking_read 代替 read ,如果参数 cont 表示输入缓冲
还有剩余数据
这种方法边沿触发方式可以正常工作边沿触发好处就是能够减少事件
次数相对,select 系统调用调用次数增加此外每次调用 read 系统调用
一下还有剩下数据”,感觉
# include <sys/time.h>
# include <sys/types.h>
int
checking_read(int fd, char buf, int len, int cont)* *
{
int n;
cont = 0; /
初始化 /* * *
n = read(fd, buf, len); /
调用 read(2) /* *
if (n > 0) { /
读取成功 /* *
fd_set fds;
struct timeval tv;
int c;
FD_ZERO(&fds); /
准备调用select(2) /* *
FD_SET(fd, &fds);
tv.tv_sec = 0; /
不会阻塞 /* *
tv.tv_usec = 0;
c = select(fd+1, &fds, NULL, NULL, &tv);
if (c == 1) { /
返回 1=缓冲 /* *
cont = 1; * /
设置继续标志 /* *
}
}
return n;
}
void
callback(int fd, void data)*
{
char buf[BUFSIZ];
int n, cont;
for (;;) { / *
n = checking_read(fd, buf, BUFSIZ, &cont);
if (n < 0) return -1; /
失败 /* *
if (n == 0) return 0; / EOF /* *
output(fd, buf, n); /
/* *
if (!cont) continue; /
读取完毕退出 /* *
}
return 1; /
成功 /* *
}
6  read(2)实现输入操作(ver.3)
320
----------------------- Page 329-----------------------
6.3 
阻塞 I/O
使用 read+O_NONBLOCK 标志
毕竟 read 系统调用可以直接接触输入缓冲那么理所当然读取数据之后应该
缓冲是否还有剩余内容那么不能实现调用 read ,发生阻塞通知一下
这样功能
当然可以只要文件描述设置O_NONBLOCK 标志输入输出发生阻塞
系统调用产生继续执行的话发生阻塞错误消息提示这个功能 UNIX
系统具备使用 O_NONBLOCK 版本 7
# include <fcntl.h>
# include <errno.h>
/ (a)
初始化程序地方 /* *
inf fl;
fl = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, fl|O_NONBLOCK);
/
到此为止 /* *
void
callback(int fd, void data)*
{
char buf[BUFSIZ];
int n;
for (;;) { / *
n = read(fd, buf, BUFSIZ);
if (n < 0) {
if (errno == EAGAIN) { / EAGAIN=
缓冲 /* *
return 1; /
读取操作结束 /* *
}
return -1; /
失败 /* *
}
if (n == 0) return 0; / EOF /* *
output(fd, buf, n); /
/* *
}
}
7  read(2) 实现输入操作(ver.4)
怎么样由于我们能够本来拥有信息 read 直接收到通知整体 6 版本
简洁许多
这个功能文件描述设置 O_NONBLOCK 标志有效因此文件描述
进行初始化操作不要忘记使用 7(a) 中的代码标志进行设置
321
----------------------- Page 330-----------------------
6 时代编程
这种方法效率代码简洁可以非常优秀有一点需要注意就是大多数输入
程序编写没有考虑文件描述设置 O_NONBLOCK 标志情况
设置 O_NONBLOCK 标志文件描述有可能发生阻塞返回错误不会
发生实质上阻塞一般输入输出程序没有想到这种行为因此发生这样错误
认为读取失败从而引发输入输出操作整体失败
使用 O_NONBLOCK 标志一定要注意文件描述使用。O_NONBLOCK 标志继承
进程因此使用 fork 时候尤其注意以前曾经遇到这样 bug :①标准输入
O_NONBLOCK ;② system 启动命令;③命令支持 O_NONBLOCK ,导致诡异错误
那时由于忘记进程继承 O_NONBLOCK 标志结果大量时间找到错误
原因
Ruby
阻塞 I/O
刚才我们 C 语言中的阻塞 I/O 进行介绍下面我们简单介绍一下 Ruby 阻塞 I/O。
Ruby
1.8.7 版本开始提供这里介绍实现阻塞 I/O 方法 read_partial
read_nonblock 。
read_partial
方法可以当前输入缓冲中的数据全部读取出来。read_partial 可以指定读取
数据长度使用方法
str = io.read_partial (1024)
read_partial
基本上不会发生阻塞输入缓冲没有读取 EOF 发生阻塞
也就是说开始原本没有数据到达情况发生阻塞
换句话说只要通过事件触发回调使用 read_partial 肯定不会发生阻塞
read_partial 实现相当于 read+select 组合
6 程序改用 Ruby 实现 8 def callback(io, data)
不过 6 程序不同数据 input = io.read_partial(4096)
output(io, input)
指定长度不会循环读取读取 end
数据长度等于长度如果循环调用 read_ 8 使用 read_partial 示例
partial
有可能发生阻塞真是难题
,read_nonblock read+O_NONBLOCK 。read_nonblock io
322
----------------------- Page 331-----------------------
6.3 
阻塞 I/O
O_NONBLOCK read 。read_ def callback(io, data)
nonblock
IO :: loop do
begin
WaitReadable
模块包含异常。 input = io.read_nonblock(4096)
output(io, input)
read_nonblock
read_partial rescue IO::WaitReadable
缓冲结束#
我们 7 程序 read_nonblock 改写 return
Ruby
程序 9 )。 C 语言版本相比,Ruby end
end
显得简洁而且 read_nonblock 自动设置 end
O_NONBLOCK
标志因此需要进行特别 9 使用 read_nonblock 例子
操作
使用 aio_read 方法
POSIX ①
提供用于异步 I/O “aio_XXXX” 1 异步I/O函数
函数 1 )。例如,aio_read 用于异步方式      
实现 read 系统调用相同功能这里 aio aio_read 异步 read
aio_write
异步 write
异步 I/O (Asynchronous I/O )缩写
aio_fsync
异步 fsync
aio
函数功能通常情况发生 aio_error 获取错误状态
aio_return
获取返回
系统调用(read、write、fsync )后台进行执行
aio_cancel
请求取消
这些函数负责发出执行系统调用请求因此
aio_suspend
请求等待
肯定不会发生阻塞
运用 aio_read 简单示例程序 10 功能非常简单
‰
打开文件
‰
aio_read 发出读取请求
‰
aio_suspend 等待执行结束
‰
或者 aio_error 检查执行结束
‰
aio_return 获取返回
下面我们看看程序具体内容
① POSIX (Portable Operating System Interface X ,
可移植操作系统接口电气电子工程师学会(IEEE )制定
一套各种 UNIX 操作系统 API 相互关联标准正式名称 IEEE 1003 。
323
----------------------- Page 332-----------------------
6 时代编程
1 /
异步 I/O所需文件 /* *
2 # include <aio.h>
3
4 /
其他文件 /* *
5 # include <unistd.h>
6 # include <string.h>
7 # include <stdio.h>
8 # include <errno.h>
9
10 int
11 main()
12 {
13 struct aiocb cb;
14 const struct aiocb cblist[1];*
15 char buf[BUFSIZ];
16 int fd, n;
17
18 /
准备文件描述 /* *
19 fd = open("/tmp/a.c", O_RDONLY);
20
21 /
初始化控制结构 /* *
22 memset(&cb, 0, sizeof(struct aiocb)); /
清空 /* *
23 cb.aio_fildes = fd; /
设置fd /* *
24 cb.aio_buf = buf; /
设置buf /* *
25 cb.aio_nbytes = BUFSIZ; /
设置buf长度 /* *
26
27 n = aio_read(&cb); /
请求 /* *
28 if (n < 0) perror("aio_read"); /
请求失败检查 /* *
29
30 # if 1
31 /
使用 aio_suspend检查请求完成 /* *
32 cblist[0] = &cb;
33 n = aio_suspend(cblist, 1, NULL);
34 # else 1
35 /
使用 aio_error检查执行完成情况 /* *
36 /
未完成返回EINPROGRESS /* *
37 while (aio_error(&cb) == EINPROGRESS)
38 printf("retry\n");
39 # endif
40
41 /
执行完成取出系统调用返回 /* *
42 n = aio_return(&cb);
43 if (n < 0) perror("aio_return");
44
45 /
读取数据存在aio_buf /* *
46 printf("%d %d ---\n%. s", n, cb.aio_nbytes, cb.aio_nbytes, cb.aio_buf);*
47 return 0;
48 }
10 异步 I/O 示例
19 行将文件 open 准备文件描述不过只是例子没有什么意义因为
实际异步 I/O 往往是以对象根据资料 HP-UX 系统,aio_
324
----------------------- Page 333-----------------------
6.3 
阻塞 I/O
read
甚至支持
22 开始作为 aio_read 参数使用控制(aiocb )结构进行初始化操作
read
系统调用 3 参数文件描述读取缓冲缓冲长度 aio_read 上述
参数分别通过 aiocb 结构 aio_fildes、aio_buf、aio_nbytes 3 成员进行设置。aiocb
结构还有其他一些成员保险起见我们 memset 它们初始化 0 (22 )。
随后我们使用 aiocb 结构通过 aio_read 函数预约执行 read 系统调用 27 )。aio_
read
只是提交请求不会等待读取过程结束实际读取数据处理
读取结束之后进行
这里我们使用 aio_suspend 执行挂起直到提交任意请求执行完毕位置 33 )。
不过话说回来我们提交请求而已
请求执行完毕检查可以使用 aio_error 实现使用提交请求 aiocb 结构调用
aio_error
函数如果请求未完成返回 EINPROGRESS ,成功完成返回 0 ,发生其他错误
相应 errno 这里处理器标明执行代码(34 ~39 ),代码使
aio_error 循环检查请求是否完成循环(busy loop ),造成无谓CPU 消耗
因此实际代码应该使用
读取请求完成之后读取数据进行处理(42 ~46 )。read 系统调用返回
可以通过 aio_return 函数获取此外读取数据保存 aiocb 结构 aio_buf
指向数组
10 程序使用 aio_suspend aio_error 检查请求是否完成其实异步 I/O
读取完成时调用回调函数功能回调函数调用信号线程方式下面
简单起见我们介绍使用线程进行回调方式 11 )。
11 程序 10 程序基本上相同不同在于回调函数指定(48 ~50 )、
回调函数(10 ~31 以及处理交给回调函数停止线程 select (56 )。
1 /
异步 I/O所需文件 /* *
2 # include <aio.h>
3
4 /
其他文件 /* *
5 # include <unistd.h>
6 # include <string.h>
7 # include <stdio.h>
8 # include <errno.h>
11 异步 I/O 示例回调
325
----------------------- Page 334-----------------------
6 时代编程
9
10 static void
11 read_done(sigval_t sigval)
12 {
13 struct aiocb cb;*
14 int n;
15
16 cb = (struct aiocb )sigval.sival_ptr;*
17
18 /
检查请求错误状态 /* *
19 if (aio_error(cb) == 0) {
20 /
获取完成系统调用返回 /* *
21 n = aio_return(cb);
22 if (n < 0) perror("aio_return");
23
24 /
读取数据存放aio_buf /* *
25 printf("%d %d ---\n%. s", n, cb->aio_nbytes, cb->aio_nbytes, cb->aio_buf);*
26
27 /
示例到此结束 /* *
28 exit(0);
29 }
30 return;
31 }
32
33 int
34 main()
35 {
36 struct aiocb cb;
37 char buf[BUFSIZ];
38 int fd, n;
39
40 /
准备文件描述 /* *
41 fd = open("/tmp/a.c", O_RDONLY);
42
43 /
初始化控制结构 /* *
44 memset(&cb, 0, sizeof(struct aiocb)); /
清空 /* *
45 cb.aio_fildes = fd; /
设置fd /* *
46 cb.aio_buf = buf; /
设置buf /* *
47 cb.aio_nbytes = BUFSIZ; /
设置buf长度 /* *
48 cb.aio_sigevent.sigev_notify = SIGEV_THREAD;
49 cb.aio_sigevent.sigev_notify_function = read_done;
50 cb.aio_sigevent.sigev_value.sival_ptr = &cb;
51
52 n = aio_read(&cb); /
请求 /* *
53 if (n < 0) perror("aio_read"); /
请求失败检查 /* *
54
55 /
停止线程处理交给回调函数 /* *
56 select(0, NULL, NULL, NULL, NULL);
57 return 0;
58 }
11 异步 I/O 示例
326
----------------------- Page 335-----------------------
6.3 
阻塞 I/O
/
异步 I/O所需文件 /* *
# include <aio.h>
/
其他文件 /* *
# include <stdlib.h>
# include <stdio.h>
# include <string.h>
# include <sys/socket.h>
# include <netinet/in.h>
static void
read_done(sigval_t sigval)
{
struct aiocb cb;*
int n;
cb = (struct aiocb )sigval.sival_ptr;*
if (aio_error(cb) == 0) {
/
获取完成系统调用返回 /* *
n = aio_return(cb);
if (n == 0) { / EOF /* *
printf("client %d gone\n", cb->aio_fildes);
aio_cancel(cb->aio_fildes, cb); /
取消提交请求 /* *
close(cb->aio_fildes); /
关闭fd /* *
free(cb); /
释放cb结构 /* *
return;
}
printf("client %d (%d)\n", cb->aio_fildes, n);
/
直接 /* *
/
读取数据存放aio_buf /* *
/
严格来说write可能阻塞这里我们忽略一点 /* *
write(cb->aio_fildes, (void )cb->aio_buf, n);*
aio_read(cb);
}
else { /
错误 /* *
perror("aio_return");
return;
}
return;
}
static void
register_read(int fd)
{
struct aiocb cb;*
char buf;*
printf("client %d registered\n", fd);
cb = malloc(sizeof(struct aiocb));
buf = malloc(BUFSIZ);
12 使用 aio_read echo 服务器节选
327
----------------------- Page 336-----------------------
6 时代编程
/
初始化控制结构 /* *
memset(cb, 0, sizeof(struct aiocb)); /
清空 /* *
cb->aio_fildes = fd; /
设置fd /* *
cb->aio_buf = buf; /
设置buf /* *
cb->aio_nbytes = BUFSIZ; /
设置buf长度 /* *
cb->aio_sigevent.sigev_notify = SIGEV_THREAD;
cb->aio_sigevent.sigev_notify_function = read_done;
cb->aio_sigevent.sigev_value.sival_ptr = cb;
/
提交请求 /* *
aio_read(cb);
}
int
main()
{
struct sockaddr_in addr;
int s = socket(PF_INET, SOCK_STREAM, 0);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(9989);
if (bind(s, (struct sockaddr )&addr, sizeof(addr)) < 0) {*
perror("bind");
exit(1);
}
listen(s, 5);
for (;;) {
/
接受来自客户端连接 /* *
int c = accept(s, NULL, 0);
if (c < 0) continue;
/
开始监听客户端 /* *
register_read(c);
}
}
12 使用 aio_read echo 服务器
由于这里我们需要回调函数调用进行一次等待因此初始化完毕 select 进行
等待回调函数使用 exit 。实际事件驱动服务器程序程序应该接受客户端
连接注册回调函数这样循环此外回调函数最后应该 exit ,而是通过 aio_
read
再次提交 read 请求用于再次读取数据
如果 SIGEV_THREAD 设置 回调函数调用 aio_read ,系统内部实际上
线程实现异步 I/O。也就是说回调函数独立主线另一单独线程执行
因此一旦使用 SIGEV_THREAD ,回调函数不能调用线程安全函数
328
----------------------- Page 337-----------------------
6.3 
阻塞 I/O
只要修改全局状态函数是非线程安全。POSIX 标准函数是非线
安全如果回调函数直接间接地调这些函数有可能引发意想不到问题
SIGEV_THREAD
线程实现回调并不所有输入处理都会使用独立线程
因此不必担心线程数量出现爆发性地增长
最后为了大家如何异步 I/O 编写事件驱动程序直观印象 12
展示使用 aio_read 手段实现 echo 服务器代码节选通过上面讲解大家是否
使用 libev 手段情况如何实现事件驱动编程有了一些了解
Linux
提供 io_getevents 其他异步 I/O 函数这些函数性能应该一些
不过它们 Linux 专用而且事件响应只能支持通常文件使用起来制约比较
中的介绍还是 POSIX 定义各种平台上都能够使用 aio_read 为主
329
----------------------- Page 338-----------------------
6 时代编程
6.4 node.js
1990
大学毕业进入软件开发公司工作到现在已经 20 不由得感叹日月如梭
20 年间从事软件开发工作感受就是无论软件开发还是其他工作重要就是
提高效率工作大山一样面前解决然而自己拥有
能力时间有限规定期限解决所有问题非常困难
话说 回来 20 工作生涯几乎没有开发客户直接使用软件作为
似乎奇葩不过依然程序员软件开发程序员能够学到丰富
东西软件开发中学如何提高效率作为应用总结下面几个方法
‰
减负
‰
拖延
‰
委派
看起来好像浑水摸鱼歪门邪道其实这些方法对于提高工作效率非常有用
减负
计算机历史上提高处理速度有效手段就是电脑计算机性能
提升而且价格越来越便宜更新硬件能够获得成倍性能提升并不稀奇
不过遗憾摩尔定律并不适用人类人类能力不可能工作角度
上面办法行不通然而如果原地踏步的话晚会年轻工资便宜
取代效率至少项目成本降低不过对于可不是什么值得高兴
正经软件开发如果更换硬件可以以下方法改善软件运行速度
‰
采用算法
‰
减少无谓开销
‰
空间时间
330
----------------------- Page 339-----------------------
6.4 node.js
如果这些方法拿到人类工作那么采用算法相当于思考高效
方式;“减少无谓开销相当于减少低级重复劳动计算机实现自动化
空间时间可能不是容易理解计算机即便进行重复劳动不会任何怨言
还是需要人类进行管理如果能够计算一次结果存在地方可以缩短计算时间
这样一来需要内存空间增加计算时间可以得到缩短人类工作
相当于复杂步骤判断实现总结成文从而提高效率方法
然而有限条件提高工作效率最好方法就是减负我们遇到大部分工作
分为非得完成不可完成不是必需以及干脆做为有趣
工作之间区别并非外人想象那样简单有一些工作虽然看起来绝对
仔细的话发现未必
人类工作定义比起计算机更加模棱两可这样伴随确定性惯性思维
不必要不紧急工作如果能够的话能够大幅度提高工作效率
拖延
减少不必要不紧急工作能够完成必要工作提高效率关于一点恐怕
没有什么异议不过到底工作必要工作不是必要区分它们可比
想象困难找出剔除不必要工作那么容易
其实做出明确区分还是诀窍就是利用人类心理弱点人们普遍
眼前忽略大局毛病因此项目期限逼近产生只要赶上工期宁愿砸锅卖铁
这样念头
即便如此估计解决不了问题还不如将计就计干脆不能为止这样一来
工期肯定赶不上只好看看哪些工作真正必需剩下那些平时
一些工作有一些抵触理由这个说不定以后”、“之前一直这么
之类工期大限压力面前这些理由完全不住就是拖延魔力
不过这个方法副作用还是危险万一估算时间必要工作完成不了
可就 只是开玩笑另一半可是真的拖延用法除此之外
其他一些利用拖延方法
例如任务各自需要一定时间才能完成有些任务只要 5 分钟完成有些
331
----------------------- Page 340-----------------------
6 时代编程
需要几个如果能够实现列出任务优先级所需时间可以利用会议之间
碎片时间完成一些小的工作
这种思路 CPU 中的执行如出一辙进一步对于任务可以按照非常
紧急必须马上完成工作”、“只要忘记什么时候完成可以工作分成个子任务
这样按照紧急程度完成任务的话可以进一步提高自己工作效率这里
执行一样需要注意任务之间相互依赖关系相互依赖关系即使改变
这些任务顺序无法提高效率一点无论现实工作还是 CPU 中都相通
委派
大多数无法同时完成多个任务因此可以看成只有单一核心硬件即便拖延
手段提高工作效率 由于同时只能处理任务每天 24 小时这个时间固定不变
因此一个人完成工作总量存在极限
这种时候,“委派成了有效手段。“委派这个印象可能不太好
就是自己能力无法处理任务转而借用他人力量完成意思如果说协作
协调团队合作的话大概委派印象要好一些起来代替
处理能力方法如出一辙
不过一样这种委派做法遇到一些困难困难大概下面
‰
任务分割
‰
通信开销
‰
可靠性
这些问题无论编程还是现实工作中都共通
如何进行妥善任务分割难题如果处理集中核心或者人员
降低然而事先处理需要时间做出完全预测困难尤其是
其他任务相互依赖关系情况可能无论如何分割无法提高工作效率
我们可以任务分为存在后续处理任务完成时需要获得通知同步任务”;
开始不必关心完成情况异步任务”。同步任务意味着存在依赖关系委派效果
明显因此如何工作分割异步任务成了提高效率关键
332
----------------------- Page 341-----------------------
6.4 node.js
成员参与项目通信开销沟通开销不可小觑人类世界由于
传达传达”、“产生误会原因导致通信开销编程世界更为显著
导致软件开发项目失败原因正是由于没有这种沟通开销引起足够重视
最后问题就是可靠性”。固然一个人工作的话可能因为生病导致工作无法
一种风险随着参与项目人数增加成员之中有人发生问题概率随之上升
这就好比只有电脑往往担心故障管理数据中心服务器
必须每天机器故障情况准备
项目规模增大万一有人中途无法工作需要考虑如何修复问题当然分布
编程一样道理
阻塞编程
编程世界减负拖延委派非常重要特别拖延委派恐怕大家
熟悉今后应该愈发成为一种重要编程技巧下面我们介绍一下编程限度
利用拖延方法然后介绍一下运用委派方法
如果程序运行时间进行详细分析可以看出大多数程序运行时其中大半时间
CPU
无所事事实际上程序大部分运行时间消耗等待输入数据环节
就是说等待消耗大量 CPU 时间
这样等待称为阻塞(blocking ),阻塞编程目的正是试图阻塞控制最低限度
下面我们介绍一下作为阻塞编程框架备受目的“node.js ”。这里我们使用 JavaScript
进行讲解
node.js
框架
node.js
一种用于 JavaScript 事件驱动框架提到 JavaScript ,大家知道一种嵌入
浏览器工作客户端环境编程语言 node.js 却是服务器工作
默认嵌入各种浏览器中的客户端语言恐怕只有 JavaScript 一种服务器
语言选择更为自由那么为什么使用 JavaScript 那是因为服务器使用
JavaScript
有的好处
333
----------------------- Page 342-----------------------
6 时代编程
首先性能随着 Google Chrome v8 引擎出现浏览器 JavaScript 引擎性能
方面竞争愈演愈烈可以,JavaScript 目前动态语言处理性能最高一种 node.js
搭载高性能著称 Google Chrome v8 引擎
其次,JavaScript 功能好处其他一些独立语言 Ruby
Python
不同,JavaScript 原本就是作为浏览器嵌入语言诞生甚至没有提供标准文件
I/O
功能
然而事件驱动框架编程通常输入输出可能产生等待非常麻烦
我们详细讲解一点。node.js 搭载 JavaScript 引擎本身没有提供可能产生阻塞
功能因此小心造成阻塞风险相应减小当然循环异常占用 CPU 导致
还是无法避免
事件驱动编程
下面我们介绍一下 node.js 这样事件驱动框架应该如何编程传统过程
编程操作按照预先设定顺序执行 1 )。人类完成工作一般方式
因此容易理解
相对事件驱动框架提供事件驱动编程存在事先设定工作顺序而是
外部事件作出响应调用事件相对回调函数”。这里事件
来自外部输入”、“到达事先设定时间”、“发生异常情况情况事件循环框架
循环程序一般循环等待事件发生检测事件发生找到启动事件
相对处理程序回调函数)。回调函数运行完毕再次回到循环等待下一个事件
2 )。
事件
操作A 输入
网络传输数据 回调函数
到达
操作B
文件输入完成 回调函数
事件循环
操作C 输出 到达指定
时间 回调函数
1 过程编程 2 事件驱动编程
334
----------------------- Page 343-----------------------
6.4 node.js
我们可以认为过程编程类似单独员工完成工作方式事件驱动编程
公司整体完成工作方式发生客户下订单事件销售部门事件循环接到
订单工作交给业务部门回调函数完成事件驱动编程模型异曲同工
事件循环利弊
实现事件循环相同功能除了回调函数之外可以采用启动线程方式不过
回调只是一种普通函数调用相比之下线程启动需要开销而且线程
需要占用一定空间(Linux 认为线程 8MB 左右)。
当然我们可以使用线程技术事先准备一些线程分配回调函数使用即便如此
资源消耗方面还是线程方式具有优势此外使用线程方式不必线程那样
排他处理由此引发问题相对
另一方面线程方式缺点虽然线程量化方面具备优势同时意味
无法利用此外如果回调函数小心产生阻塞导致事件处理整体停滞
线程 / 线程方式存在这样问题
node.js
编程
无论 Debian GNU/Linux 还是使用 sid ① (开发提供node.js 软件包
安装方法如下
# apt-get install nodejs
如果使用发行没有提供软件包需要源代码安装其他大多数开源
一样,node.js 编译安装 configure、make、make install 标准步骤进行
安装完毕可以使用 node 命令 node.js 主体不带任何参数启动 node 命令
进入下面这样交互模式
% node
> console.log("hello world")
hello world
① sid
Debian 不稳定版本开发版本代号:http://www.debian.org/releases/sid/ 。
335
----------------------- Page 344-----------------------
6 时代编程
console.log
用于交互模式进行输出函数交互模式应该使用标准输出因此
可以认为正式环境不会使用不过如果确认 node.js 是否工作正常这个函数是非
方便这里我们基本上交互模式进行讲解因此经常使用 console.log 函数
下面我们引发事件试试看设置定时发生事件及其相应回调函数
可以使用 setTimeout() 函数
> setTimeout(function(){
... console.log("hello");
... }, 2000)
调用 setTimeout() 作用经过第二参数指定时间单位毫秒之后引发事件
事件发生调用第一参数指定回调函数。Timeout 事件发生一次事件
function () {
console.log("hello");
}
这个部分匿名函数 Ruby 中的 lambda 功能差不多这里重点调用 setTimeout()
函数之后函数马上执行完毕退出
setTimeout()
作用仅仅预约事件发生设置回调函数没有必要进行
等待 Ruby 中的 sleep 方法不同,node.js 持续事件进行监视基本上不会发生
阻塞
node.js
对话模式表面上似乎一直等待标准输入实际上只是标准输入传来
数据事件进行响应设置回调函数输入字符串作为 JavaScript 程序进行编译
执行因此交互模式输入 JavaScript 代码立即编译执行执行完毕再度
返回 node.js 事件循环事件处理回调函数调用事件循环完成
setTimeout()
产生发生一次事件如果产生一定间隔重复发生事件可以
使用“setInterval()”函数设置相应回调函数
> var iv = setInterval(functi
on(){
... console.log("hello");
... }, 2000)
hello
hello
通过 setInterval() 函数我们设置 2000 毫秒发生一次事件发生事件时调
指定回调函数不过每隔显示 hello 实在烦人我们还是这个定期
取消
336
----------------------- Page 345-----------------------
6.4 node.js
为此我们需要使用 clearInterval() 函数 setInterval() 返回作为参数调用 clearInterval()
可以解除指定定期事件
> clearInterval(iv);
node.js
网络编程
网络服务器发挥 node.js 本领最好
require "socket"
我们实现简单服务器即将
svr = TCPServer.open(8000)
接收数据原原本本返回回声服务器”。 socks = [svr]
node.js 实现回声服务器 3
loop do
result = select(socks);
作为对照我们不用事件驱动框架而是 next if result == nil
Ruby
实现另一版本回声服务器 4 。 for s in result[0]
if s == svr
ns = s.accept
我们连接一下试试看 node.js socks.push(ns)
回声服务器需要 3 中的程序保存文件 else
if s.eof?
echo.js ,然后执行: s.close socks.delete(s)
elsif str =
s.readpartial(1024)
var net = require("net"); s.write(str)
net.createServer(function(sock){ end
sock.on("data", function(data) { end
sock.write(data); end
});}).listen(8000); end
3  node.js 实现回声服务器 4  Ruby 实现回声服务器
% node echo.js
可以启动程序。Ruby 可以 4 程序保存 echo.rb ,执行
% ruby echo.rb
客户端我们直接 netcat 无论使用 node.js 还是 Ruby 可以通过下列
命令连接
% netcat localhost 8000
连接只要键盘输入字符得到一行输入字符相同输出结果结束 telnet 会话
可以使用“Ctrl+C”组合
337
----------------------- Page 346-----------------------
6 时代编程
3 程序 4 程序对比一下发现不同首先 4 Ruby 程序实际上
实现相当于事件循环部分监听注册删除管理工作自行完成
导致代码规模变得相对较大
node.js
Ruby 简洁许多虽说采用 node.js 需要熟悉回调风格作为服务器
实现显然还是事件驱动框架更加高效
下面我们详细看看 node.js Ruby 之间区别
首先,node.js 开头使用 require 函数引用 net ,net 提供通信相关
接下来调用 net.createServer 函数用于创建 TCP/IP 服务器
接受来自客户端的连接请求 createServer 参数指定函数作为回调函数进行
回调函数调用客户端连接(sock )作为参数传递。sock on
用于设置 sock 相关事件回调函数
客户端的数据到达发生data 事件收到数据传递回调函数这里我们
实现回声服务器因此需要收到数据原本返回即可。listen 方法用于服务器
监听指定口号
随后程序到达末尾进入事件循环需要注意,node.js 程序 程序主体负责
事件回调函数进行设置初始化实际处理事件循环完成
相对,Ruby 需要自行管理事件循环因此程序结构相对复杂一些大体上
这样
(1)
通过 TCPSever.open 打开服务器
(2)
通过 select 等待事件
(3)
如果服务器产生事件通过 accept 打开客户端连接
(4)
除此之外事件遇到 eof? (连接结束关闭客户端
(5)
读取数据数据原原本本客户端
node.js 上述 (2)、(3)、(4) 部分已经嵌入框架需要代码编写而且
程序员不必关心资源管理细节正是由于这些特性使得回声服务器实现,node.js
代码能够做到非常简洁
338
----------------------- Page 347-----------------------
6.4 node.js
node.js
回调风格
3 这样多个回调函数叠加起来编程风格恐怕还是习惯一下才能上手
下面我们通过实现简单 HTTP 服务器仔细探讨一下回调风格 5 运用 node.
js
实现简单 HTTP 服务器无论收到任何请求返回 hello world 这个
字符串
var http = require('http');
http.createServer(function(req, res) {
res.writeHead(200, {'Content-Type':'text/plain'});
res.write("hello world¥n");
res.end();
}).listen(8000);
5  node.js 编写 HTTP 服务器(1)
我们实际访问一下试试看
% curl http://localhost:8000/
hello world
简单
我们思考一下回调风格 6 读取当前目录 index.html 文件返回
内容 HTTP 服务器。index.html 读取 fs readFile 函数完成
这个函数文件读取完毕调用回调函数也就是说便是简单文件输入输出采用
回调风格传递回调函数参数包括是否发生错误信息以及读取字符串
node.js
fs 提供用于同步读取操作 readFileSync 函数 node.js 还是
推荐采用阻塞风险回调风格
这样随着接受请求读取文件操作叠加回调函数嵌套越来越
风格面临课题当然我们方法应对不过关于这个问题我们还是将来
机会
339
----------------------- Page 348-----------------------
6 时代编程
var http = require("http");
var fs = require("fs");
http.createServer(function(req, res) {
fs.readFile("index.html", function(err, content) {
if (err) {
res.writeHead(404, {"Content-Type":"text/plain"});
res.write("index.html: no such file¥n");
}
else {
res.writeHead(200, {"Content-Type":"text/html; charset=utf-8"});
res.write(content);
}
res.end();
});
}).listen(8000);
6  node.js 编写 HTTP 服务器(2)
node.js
优越
通过刚才介绍大家是不是能够感觉到 node.js 可以容易实现互联网服务器
即使必须习惯 node.js 回调风格这样特性非常诱人
不过 node.js 实现服务器优越并非只有容易一点而已首先事件检测
node.js
没有采用连接增长速度逐渐 select 系统调用传统方式而是采用
连接无关能够维持一定性能 epoll (Linux )kqueue (FreeBSD )方式因此
上限方面可以比较令人放心但是对于进程文件描述数量操作系统
存在上限缓解上限可能需要一些额外设置
其次,node.js http 采用 HTTP1.1 keep-alive 方式来自同一客户端的连接可以
重复使用。TCP 连接操作需要一定开销通过连接重复使用反复访问
同一服务器可以获得性能
通过运用事件驱动模型可以减少连接消耗资源综合上述这些优势可以
同一客户端同一服务器进行频繁连接连接非常场景例如网络聊天程序
实现使用 node.js 合适
我们 3 回声服务器进行些许改造实现简单聊天服务器 7 )。这里
改造只是回声服务器返回输入字符串部分成了字符串送给当前连接
客户端另外我们连接中断发生 end 事件设置回调函数用于客户端
连接列表删除
340
----------------------- Page 349-----------------------
6.4 node.js
通过这样简单修改多个客户端连接
var net = require("net");
服务器其中任意客户端上输入信息 var clients = [];
可以所有客户端上显示出来当然作为 net.createServer(function(sock){
简易聊天程序无法显示消息 clients.push(sock);
sock.on("data", function(data) {
发送因此不是实用如果 for (var i=0; i<clients.length; i++)
{
连接获取相关信息的话修改
clients[i].write(data);
应该。 }
});
sock.on("end", function() {
此外我们这里直接使用 TCP 连接, var i = clients.indexOf(sock);
只要运用 keep-alive Ajax (Asynchronous clients.splice(i, 1);
});
JavaScript and XML )
技术HTTP 实现 }).listen(8000);
实时聊天也就是所谓 COMET )并非 7  node.js 编写网络聊天程序
能够轻松开发负担如此大量连接互联网
服务器正是 node.js 事件驱动框架
所在
EventMachine
Rev
当然面向 Ruby 事件驱动框架存在其中代表性 EventMachine
Rev 。EventMachine
面向 Ruby 事件驱动框架中的元老实际上 node.js 官方介绍
EventMachine”①这样说法之所以这里没有介绍它们因为相比这些框架
提供事件启动相应对象方法方式,node.js 这样注册回调函数方式更加
容易讲解
① “Node is similar in design to and influenced by systems like Ruby s Event Machine or Python s Twisted.”(http://nodejs .’ ’
org/about/ )
341
----------------------- Page 350-----------------------
6 时代编程
6.5 ZeroMQ
大家记得 6.4 中学那些提高工作效率关键词就是拖延委派所谓拖延
就是工作分解小的任务无法马上着手工作后面从而减少等待无用
时间提高整体工作效率 6.4 我们通过 node.js 彻底杜绝等待阻塞框架
拖延策略进行具体实践
然而无论如何削减无谓等待单位时间每个人所能完成工作量或者
CPU
所能处理工作量存在极限因此我们需要另一策略委派也就是说
工作交给个人或者多个 CPU 共同分担从而提升整体处理效率
接下来我们具体学习一下委派策略编程委派意味着充分运用多个 CPU 进行
分布式处理
CPU 必要性
CPU 系统需求主要出于原因 CPU 摩尔定律影响发展趋势
绝对性能需求
关于前者摩尔定律影响一直以来 CPU 性能提升通过晶体管数量增加
实现随着渗漏电流问题形成障碍这种传统性能提升方式达到极限
于是一块芯片搭载多个 CPU 核心技术便应运而生
最近电脑多个程序同时工作任务方式已经成为主流因此如果 CPU 拥有多个
能够进行并行处理那么必然带来直接性能提升美国英特尔公司推出 Core i7 CPU
4 物理核心通过线程技术对外能够体现 8 核心面向普通电脑 CPU 做到
地步已经着实令人惊叹
既然普通电脑已经配备 CPU 那么积极运用技术提高工作效率
然而过去 CPU 本身性能提升不同环境提升处理性能软件方面
必须支持
342
----------------------- Page 351-----------------------
6.5 ZeroMQ
产生系统需求原因前者属于环境问题存在 CPU ,因此想要
进行活用这样态度后者绝对处理性能需求可以一种刚性需求
叫做信息爆炸”,也就是说我们每天接触数据正在不断增加各种设备通过
接到电脑网络不断获取信息原本孤立存在数据通过互联网开始相互流通
变化影响我们每天接触数据正在飞速增长既然单个 CPU 性能
提升已经到了瓶颈那么通过捆绑多个 CPU 提升性能可以必然趋势
无论如何可以提升处理性能充分运用 CPU 毫无悬念为此必须开发
能够活用 CPU 软件
定律
不过首先大家必须记住一点某种意义理所当然就是即便活用
CPU
开发相应软件不可能无限提升工作效率
定律就是前面我们已经定律吉恩·(Gene
Amdahl )
提出内容:“(通过并行计算获得系统性能提升效果随着无法并行
部分产生饱和”。
能够活用 CPU 处理基本上可以分解下列步骤
(1)
数据分割分配
(2)
分配数据进行并行处理
(3)
处理数据进行集约
其中能够并行处理部分基本上只有 (2) ,数据分割集约无论多少 CPU ,
限度运用它们性能
CPU 运用方法
运用 CPU 手段大体上可以分成线程方式进程方式
除此之外有一些支持分布式编程框架内部原理还是采用线程进程
方式中的一种
343
----------------------- Page 352-----------------------
6 时代编程
线程进程 CPU 有效运用手段各自拥有不同性质拥有各自优点
应该根据应用程序性质进行选择
线程进程工作控制流程特点工作同一进程多个线程
之间其内空间共享特点可以喜忧参半决定线程性格
共享内存空间意味着线程操作数据需要进行复制尤其是线程需要操作
数据非常多情况这样无需复制能够传递数据处理性能方面非常有利
然而获得上述好处同时带来一定隐患共享内存空间意味着线程
操作数据结构可能其他线程修改由于线程独立工作因此可能导致一些
时机出现非常难以发现 bug 。
虽然大多数情况不会出问题然而非常偶然情况线程同时访问同一数据结构
导致程序崩溃而且这样 bug 重现想到可能寻找这样 bug ,会不
感到眼前
此外线程进程工作控制流程反过来说所有处理必须同一进程
完成意味着如果采用线程方式运用 CPU ,必须电脑完成所有
处理
然而便是已经司空见惯现在电脑能够使用核心数量最多也就是 4
线程也就是 8 服务器的话可能配备核心无论如何现在无法达到
甚至核心规模如果实现并行线程还是遇到极限
相对进程方式同样喜忧参半特点正好线程方式相反无法共享内存空间
处理不会局限计算机完成
无法共享内存空间意味着操作数据需要进行复制操作性能不利影响但是
并发编程数据共享一向引发问题罪魁祸首因此牺牲性能换取安全性角度
可以算是优点
此外刚才提到计算机搭载 CPU 数量情况使用进程方式能够
通过计算机构成系统运用 CPU 进行并行处理优点不过
场景选择手段实现进程数据交换显得非常重要
尽管个人喜好可能并不可靠相比线程方式更加倾向于使用进程方式
理由首先安全使用线程相当困难相比共享内存带来性能提升由于
344
----------------------- Page 353-----------------------
6.5 ZeroMQ
状态共享导致一些 bug ,因此风险大于好处
其次性能提升带来贡献受到计算机搭载 CPU 核心数量上限制约因此
扩展相对换句话说可能得很辛苦得不到好处性价比
当然某些情况线程方式进程方式合适不是线程方式世界消失
不过认为线程方式应该有限情况而且一般用户看不见地方
应用程序架构模型尺度使用当然知道一定有人不同看法
进程通信
由于线程共享内存空间因此不会发生所谓通信反过来说存在如何防止
进程同时访问数据排他控制问题
相对由于进程之间共享数据因此需要进行通信进程通信手段多种
其中具有代表性下列
‰
管道
‰ SysV IPC
‰ TCP

‰ UDP

‰ UNIX

下面我们分别简单介绍一下
管道
所谓管道就是能够输出然后另一读取文件描述。Shell 中的管道
通过方式实现
文件描述进程独立存在创建进程继承进程所有文件描述
因此可以用于具有父子兄弟关系进程之间进行通信
例如具有父子关系进程之间进行管道通信可以下列步骤操作这里为了
起见我们进程进程进行通信
345
----------------------- Page 354-----------------------
6 时代编程
首先使用 pipe 系统调用创建一对文件描述下面我们读取一方文件描述
“r”,文件描述称为“w”。
通过 fork 系统调用创建进程
进程一方描述 w 关闭
进程一方描述 r 关闭
进程一方将要发送进程数据描述 w 。
进程一方描述 r 读取数据
为了实现进程双向通信需要上述相同步骤创建管道虽然比较麻烦
难度不大
Shell 一样个子进程之间进行通信只要创建管道分配进程进程
之间可以直接通信为了进程进程联系起来每次需要执行上述步骤一旦自己
尝试一次之后明白 Shell 多么强大
管道通信只能用于具有父子兄弟关系共享文件描述进程之间因此只能实现
电脑进程通信实际上如果使用后面介绍 UNIX 可以具有
关系进程之间传递文件描述只能同一电脑限制依然存在
SysV IPC
UNIX
System V (Five )版本引入称为SysV IPC 进程通信 API ,其中 IPC
Inter Process Communication (进程通信缩写
SysV IPC
包括下列 3 通信方式
‰
消息队列
‰
信号
‰
共享内存
消息 队列一种用于进程数据通信手段管道只是一种机制每次数据长度
信息无法保存相对消息队列可以保存消息长度
信号(semaphore )一种带有互斥计数器标志(flag )。这个原本是荷旗语
意思信号可以设定某种资源同时访问数量上限
共享 内存一块进程共享内存空间通过共享内存空间分配自身进程内存空间
346
----------------------- Page 355-----------------------
6.5 ZeroMQ
(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 同时进行等待”,一点非常方便
347
----------------------- Page 356-----------------------
6 时代编程
‰
进程结束操作系统自动释放因此无需担心资源泄漏问题
‰
由于优秀设计开始吸收 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 ,因为通信性能数据传输可靠性
348
----------------------- Page 357-----------------------
6.5 ZeroMQ
更加重要也就是说相比通话包含少许杂音还是保证小的通话延迟更加重要
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
349
----------------------- Page 358-----------------------
6 时代编程
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 ),服务器客户
350
----------------------- Page 359-----------------------
6.5 ZeroMQ
返回应答(reply )这样连接模型 1 )。 REP
服务器 客户端
作为网络连接这种方式非常常见例如 REQ
HTTP
协议遵循 REQ/REP 模型通过网络进行 1 REQ/REP 模型
函数调用 RPC (Remote Procedure Call ,远程过程调用
发布
属于一类。REQ/REP 一种双向通信
PUB
PUB/SUB
PUBLISH/SUBSCRIBE 缩写
发布(publish )信息服务器注册(subscribe , SUB SUB SUB
订阅客户端都会收到信息 2 )。这种模型
订阅 订阅 订阅
需要大量客户端一起发送通知以及数据分发部署
场合非常方便。PUB/SUB 一种单向通信 2 PUB/SUB 模型
PUSH/PULL
队列添加取出信息一种模型。PUSH/PULL 模型应用范围广
如果只有数据添加数据获取的话 类似 UNIX 管道方式使用
3a ),
如果服务器PUSH 信息台客 PULL 的话可以类似任务队列
方式使用 3b )。
3b 场景处于等待状态任务只有能够取得数据相对,PUB/SUB
所有等待进程能够取得数据
反过来说如果多个进程 PUSH ,能够用来结果进行集约3c )。PUB/SUB 一样
PUSH/PULL
一种单向通信
任务A 分配 工作进程 工作进程 工作进程
PUSH PUSH PUSH PUSH PUSH
PULL PULL PULL PULL PULL
任务B 工作进程 工作进程 工作进程 集约
(a)
管道一对一) (b)任务分配一对) (c)任务集约
3 PUSH/PULL 模型
PAIR
一种一对一双向通信说实话了解范围不清楚这种模型应该
使用
351
----------------------- Page 360-----------------------
6 时代编程
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'
require 'zmq' context = ZMQ::Context.new
socket = context.socket(ZMQ::REQ)
context = ZMQ::Context.new socket.connect("tcp://127.0.0.1:5000")
socket = context.socket(ZMQ::REP)
socket.bind("tcp://127.0.0.1:5000") for i in 1..10
msg = "msg %s" % i
loop do socket.send(msg)
msg = socket.recv print "Sending ", msg, "\n"
print "Got ", msg, "\n" msg_in = socket.recv
socket.send(msg) print "Received ", msg, "\n"
end end
4 ZeroMQ REQ/REP 服务器 5 ZeroMQ REQ/REP 客户端
这样我们完成万能 echo 服务器及其相应客户端
ZeroMQ
可以发送接收任何二进制数据如果我们发送 JSON MessagePack 字符串的话
可以轻松实现一种 RPC 功能
352
----------------------- Page 361-----------------------
6.5 ZeroMQ
进行通信可以顺序启动 4 5 程序有意思一般程序
启动服务器 ZeroMQ 程序启动客户端可以
ZeroMQ
按需连接因此连接对象尚未初始化客户端进入待机状态启动
自由一点非常方便尤其是 PUB/SUB PUSH/PULL 模型连接如果所有服务器
客户端只能按照一定顺序启动制约 ZeroMQ 可以我们这样制约
解放出来
此外,ZeroMQ 可以同时连接多个服务器如果 5 程序 5 connect 一行之后
添加一行相同语句例如改变口号),可以服务器交替发送请求通过这样
方式可以容易实现负载分配
下面我们看看 PUSH/PULL 、PUB/SUB 模型实现简单聊天程序这个示例
3 程序构成程序 1 (6 )聊天发言
require 'zmq'
程序通过命令行输入字符 PUSH
context = ZMQ::Context.new
服务器发言”。程序 2 (7 )显示
socket = context.socket(ZMQ::PUSH)
程序通过 SUBSCRIBE 方式获取 socket.connect("tcp://127.0.0.1:7900")
服务器 PUBLISH 发言信息显示屏幕 socket.send(ARGV[0])
实际聊天系统客户端应该程序
6 聊天发言程序
1
程序 2 结合
程序 3 ( 8 )聊天服务器通过PULL 接收发言数据原原本本 PUBLISH
出去凡是 SUBCRIBE 服务器客户端可以收到发言内容 9 )。无论多少
连接服务器,ZeroMQ 都会自动进行管理因此程序实现比较简洁
require 'zmq'
require 'zmq'
context = ZMQ::Context.new
context = ZMQ::Context.new receiver = context.socket(ZMQ::PULL)
socket = context.socket(ZMQ::SUB) receiver.bind("tcp://127.0.0.1:7900")
socket.connect("tcp://127.0.0.1:7901") clients = context.socket(ZMQ::PUB)
#
显示执行SUBSCRIBE操作消息进行取舍选择 clients.bind("tcp://127.0.0.1:7901")
#
字符串表示全部获取意思
socket.setsockopt(ZMQ::SUBSCRIBE, "") loop do
msg = receiver.recv
loop do printf "Got %s¥n", msg
puts socket.recv clients.send(msg)
end end
7 聊天显示程序 8 聊天服务器程序
353
----------------------- Page 362-----------------------
6 时代编程
服务器 程序3 (聊天服务器
PULL PUB
PUSH SUB
客户端
程序1 程序2
9 聊天程序工作方式
小结
ZeroMQ
简单 API 实现进程通信直接使用相比一对
通信实现比较容易 CPU 运用横跨计算机进程通信不可
或缺因此需要考虑扩展软件开发项目 ZeroMQ 这样进程通信今后
应该变得越来越重要
354
----------------------- Page 363-----------------------
时代编程后记
一句话多次各位读者可能已经听腻了不过这里还是
再说一次现在时代
所谓原本一块芯片封装多个 CPU 核心意思截至 2012 4
能够电脑基本上搭载 Intel Core i5 CPU 芯片事实这个时代
写照
并不 CPU 使用大多数情况运行
系统可以利用多个 CPU 核心这个意思这样场景并不局限于一块芯片
芯片甚至计算机组成环境可以看作按照这样理解云计算
可以一种典型环境
环境编程共同点在于传统编程风格程序顺序执行因此只能
单独核心充分发挥优势必须通过某些方法积极运用多个 CPU
处理能力
介绍一些活用多个 CPU 方法包括 UNIX 进程活用通过异步 I/O 实现
并行消息队列这些非常前途技术然而,UNIX 进程基本使用方法
只能计算机异步 I/O 虽然提高效率本身无法运用消息队列
目前没有强大能够支持数百节点规模系统构建
超级计算机现状进行推测将来云计算环境中的系统能够
达到数万节点十万核心规模构建这样系统现在技术可以实现
并非易事
因此一方面今后需要进步
355
----------------------- Page 364-----------------------
2
5 IDA 数据显示窗口

MATSUMOTO YUKIHIRO CODE NO MIRAI written by Yukihiro Matsumoto.
Copyright©2012 by Yukihiro Matsumoto
All rights reserved.
Originally published in Japan by Nikkei Business Publications, Inc.
中文简体字Nikkei Business Publications, Inc.授权人民邮电出版社独家出版未经
书面许可不得任何方式复制抄袭内容
版权所有侵权

----------------------- Page 365-----------------------

----------------------- Page 366-----------------------

----------------------- Page 367-----------------------
欢迎
图灵社区
发售平台
出版时代已经来临许多出版犹豫彷徨时候图灵社区已经
采取实际行动拥抱这个出版巨变纸质具有许多明显优势
不仅发布更新容易,⽽而且尽可能即使有的纸质
)。读者可以方便搜索复制打印
图灵社区传统出版流程出版业务紧密结合,⺫⽬目前实现译者⺴⽹网上
稿编辑⺴⽹网上审稿发布出版模式这种出版模式我们称之为
敏捷出版”,可以读者速度了解国外最新技术图书内容弥补
以往翻译技术出版过时缺憾同时敏捷出版使得
交流更为方便可以提前消灭书稿中的错误程度保证图书出版质量
开放出版平台
图灵社区读者开放在线写作功能协助实现出版梦想可以联合
共同创作技术参考书以免收费形式提供读者大地降低
门槛成熟书稿机会入选出版计划同时出版纸质
图灵社区引进出版图书立项马上社区公布如果有意翻译
图书欢迎社区申请只要通过考验即可签约成为图灵译者当然
成功完成翻译工作需要坚强
读者交流平台
图灵社区读者可以十分方便写作文章提交勘误发表评论各种方式
译者编辑人员其他读者交流互动提交勘误能够获赠社区欢迎
大家积极参与社区开展访谈审读评选多种活动取银可以