本书是作者在月刊杂志《日经Linux》上专栏《松本行弘:技术的剖析》各期(2009年6月号~2012年6月号)的合集,2013年6月第1版出版。
目录
1 编程的时间和空间
1.1 编程的本质
- 编程的本质是思考
- 创造世界的乐趣
- 快速提高的性能改变了社会
- 以不变应万变
- 摩尔定律的局限
- 社会变化和编程
1.2 未来预测
- 科学的未来预测
- IT未来预测
- 极限未来预测
- 从价格看未来
- 从性能看未来
- 从容量看未来
- 从带宽看未来
2 编程语言的过去、现在和未来
2.1 编程语言的世界
- 被历史埋没的先驱
- 编程语言的历史
- 编程语言的进化方向
- 未来的编程语言
- 20年后的编程语言
- 学生们的相像
2.2 DSL(特定领域语言)
- 外部DSL
- 内部DSL
- DSL的优势
- DSL的定义
- 适合内部DSL的语言
- 外部DSL实例
- DSL设计的构成要素
- Sinatra
2.3 元编程
- Meta, Reflection
- 类对象
- 类的操作
- Lisp
- 数据和程序
- Lisp程序
- 宏
- 宏的功和过
- 元编程的可能性和危险性
2.4 内存管理
- 看似无限的内存
- GC的三种基本方式
- 术语定义
- 标记清除方式
- 复制收集方式
- 引用计数收集方式
- 引用计数收集方式的缺点
- 进一步改良的应用方式
- 分代回收
- 对来自旧生代的引用进行记录
- 增量回收
- 并行回收
- GC大统一理论
2.5 异常处理
- “一定没问题的”
- 用特殊返回值表示错误
- 容易忽略错误处理
- Ruby中的异常处理
- 产生异常
- 更高级的异常处理
- Ruby中的后处理保证
- 其他语言中的异常处理
- Java的检查型异常
- Icon的异常和真假值
- Eiffel的Design by Contract
- 异常与错误值
2.6 闭包
- 函数对象
- 高阶函数
- 用函数参数提高通用性
- 函数指针的局限
- 作用域:变量可见范围
- 生存周期:变量的存在范围
- 闭包和面向对象
- Ruby的函数对象
- Ruby与JavaScript的区别
- Lisp-1与Lisp-2
3 编程语言的新潮流
3.1 语言的设计
- 客户端与服务器端
- 向服务器端华丽转身
- 在服务器端获得成功的四大理由
- 客户端的JavaScript
- 性能显著提升
- 服务器端的Ruby
- Ruby on Rails带来的飞跃
- 服务器端的Go
- 静态与动态
- 动态运行模式
- 何谓类型
- 静态类型的优点
- 动态类型的优点
- 有鸭子样就是鸭子Duck Typing
- Structural Subtyping
3.2 Go
- New(新特性)
- Experimental(实验性)
- Concurrent(并发性)
- Garbage-collected(垃圾回收)
- Systems(系统性)
- Go的创造者们
- Hello World
- Go的控制结构
- 类型声明
- 无继承式面向对象
- 多值与多重赋值
- 并发编程
3.3 Dart
- 为什么要推出Dart
- Dart的设计目标
- 代码示例
- Dart的特性
- 基于类的对象系统
- 非强制性静态类型
- Dart的未来
3.4 CoffeeScript
- 最普及的语言
- 被误解最多的语言
- 显著高速化的语言
- 对JavaScript的不满
- CoffeeScript
- 安装方法
- 声明和作用域
- 分号和代码块
- 省略记法
- 字符串
- 数组和循环
- 类
3.5 Lua
- 示例程序
- 数据类型
- 函数
- 表
- 元表
- 方法调用的实现
- 基于原型编程
- 和Ruby的比较(语言篇)
- 嵌入式语言Lua
- 和Ruby的比较(实现篇)
- 嵌入式Ruby
4 云计算时代的编程
4.1 可扩展性
- 信息的尺度感
- 大量数据的查找
- 二分法查找
- 散列表
- 布隆过滤器
- 一台计算机的极限
- DHT(分布式散列表)
- Roma
- MapReduce
4.2 C10K问题
- 何为C10K问题
- C10K问题所引发的“想当然”
- 使用epoll功能
- 使用libev框架
- 使用EventMachine
4.3 HashFord
- HashFord库的实现(Level 1)
- 运用多核的必要性
- 目前的Ruby实现所存在的问题
- 通过进程来实现HashFord(Level 2)
- 抖动
- 运用进程池的HashFord(Level 3)
4.4 进程间通信
- 进程与线程
- 同一台计算机上的进程间通信
- TCP/IP协议
- 用C语言进行套接字编程
- 用Ruby进行套接字编程
- Ruby的套接字功能
- 用Ruby实现网络服务器
4.5 Rack与Unicorn
- Rack中间件
- 应用程序服务器的问题
- Unicorn的架构
- Unicorn的解决方案
- 性能
- 策略
5 支撑大数据的数据存储技术
5.1 键-值存储
- Hash类
- DBM类
- 数据库的ACID特性
- CAP原理
- CAP解决方案——BASE
- 不能舍弃可用性
- 大规模环境下的键-值存储
- 访问键-值存储
- 键-值存储的节点处理
- 存储器
- 写入和读出
- 节点追加
- 故障应对
- 终止处理
- 其他机制
- 性能与应用实例
5.2 NoSQL
- RDB的极限
- NoSQL数据库的解决方案
- 形形色色的NoSQL数据库
- 面向文档数据库
- MongoDB的安装
- 启动数据库服务器
- MongoDB的数据库结构
- 数据的插入和查询
- 用JavaScript进行查询
- 高级查询
- 数据的更新和删除
- 乐观并发控制
5.3 用Ruby来控制MongoDB
- 使用Ruby驱动
- 对数据库进行操作
- 数据的插入
- 数据的查询
- 高级查询
- find方法的选项
- 原子操作
- ActiveRecord
- OD Mapper
5.4 SQL数据库的反击
- “云”的定义
- SQL数据库的极限
- 存储引擎Spider
- SQL数据库之父的反驳
- SQL数据库VoltDB
- VoltDB的架构
- VoltDB的编程
- Hello VoltDB
- 性能测试
5.5 memcached和它的伙伴们
- 用于高速访问的缓存
- memcached
- 示例成讯
- 对memcached的不满
- memcached替代服务器
- 另一种键-值存储Redis
- Redis的数据类型
- Redis的命令与示例
6 多核时代的编程
6.1 摩尔定律
- 呈几何级数增长
- 摩尔定律的内涵
- 摩尔定律的结果
摩尔定律带来的可能性
为了提高性能
- 摩尔定律的极限
- 超越极限
- 不再有免费的午餐
6.2 UNIX管道
- 管道编程
- 多核时代的管道
- xargs——另一种运行多核的方式
- 注意瓶颈
- 阿姆达尔定律
- 多核编译
- cache
- distcc
- 编译性能测试
6.3 非阻塞I/O
- 何谓非阻塞I/O
- 使用read(2)的方法
- 边沿触发和电平触发
- 使用read(2)+select的方法
- 使用read+O_NONBLOCK标志
- Ruby的非阻塞I/O
- 使用aio_read的方法
6.4 node.js
- 减负
- 拖延
- 委派
- 非阻塞编程
- node.js框架
- 事件驱动编程
- 事件循环的利弊
- node.js编程
- node.js网络编程
- node.js回调风格
- node.js的优越性
- EventMachine与Rev
6.5 ZeroMQ
- 多CPU的必要性
- 阿姆达尔定律
- 多CPU的运用方法
- 进程间通信
- 管道
- SysV IPC
- 套接字
- UNIX套接字
- ZeroMQ
- ZeroMQ的连接模型
- ZeroMQ的安装
- ZeroMQ示例程序
笔记
1 编程的时间和空间
1.1 编程的本质
- 编程语言:一种人类和计算机都能理解的语言
- 编程:通过编程语言将人类的意图传达给计算机
软件:用编程语言将计算机需要执行的操作步骤详细描述出来
摩尔定律:LSI(大规模集成电路)中的晶体管数量每18个月增加一倍。
1.2 未来预测
- 价格下降
- 性能提高
- 容量增大
- 带宽增加
2 编程语言的过去、现在和未来
2.1 编程语言的世界
- Plankalkül:赋值语句、子程序、条件判断、循环、浮点数计算、数组、结构体、断言、异常处理、目标搜寻……
- 格式:”计算=>结果保存位置”
- FORTRAN:FORmula TRANslator,算式翻译器,面向科学计算。
- COBOL:COmmon Business Oriented Language,面向商务计算。
- Lisp:基于
λ演算
的
LISt Processor
- SNOBOL:
StriNg Oriented symBOlic Language
, 处理字符串 - 数学性语言:基于一阶谓语逻辑的Prolog,函数型编程语言ML、Haskell
- 主流语言:系统描述语言C/C++,面向商务语言Java,Web领域Ruby、Perl、Python、PHP等脚本语言
2.3 元编程
6 多核时代的编程
6.1 摩尔定律
CPU高速化技术:
- 流水线(pipeline):将命令分割成多段,先后并行执行。
- 微指令编码(micro-operation decoding):不直接执行机器语言,先转换成更加细化的内部指令。
- 乱序执行(out-of-order execution):先判断指令之间的依赖关系,调整没有依赖关系的指令的执行顺序,稀释指令之间的依赖关系。
- 投机执行(speculative execution):条件分支时不等待条件判断结果,而先继续尝试执行,错误则回退。
- 高速缓存(cache):将读取过的数据存放在CPU内部存储器中。
- 多核(multi-core)
- 超线程(hyper threading)
流水线停顿(pipeline stall/ bubble)原因:
- 数据冒险,data hazard
- 资源冒险,resource hazard
- 分支冒险,branch hazard
6.3 非阻塞 I/O
- read(2):在事件循环内使用选择可读写文件描述符的“select”和“epoll”等,对文件描述符进行监视,并对数据到达的文件描述符调用对应的回调函数,应避免回调函数阻塞。
- LT(level triggered) 是默认/缺省的工作方式,同时支持 block和no_block。这种工作方式下,内核会通知你一个fd是否就绪,然后才可以对这个就绪的fd进行I/O操作。就算你没有任何操作,系统还是会继续提示fd已经就绪,不过这种工作方式出错会比较小,传统的select/poll就是这种工作方式的代表。
- ET(edge-triggered) 是高速工作方式,仅支持no_block,这种工作方式下,当fd从未就绪变为就绪时,内核会通知fd已经就绪,并且内核认为你知道该fd已经就绪,不会再次通知了,除非因为某些操作导致fd就绪状态发生变化。如果一直不对这个fd进行I/O操作,导致fd变为未就绪时,内核同样不会发送更多的通知,因为only once。所以这种方式下,出错率比较高,需要增加一些检测程序。
- select 属于电平触发;epoll 默认电平触发,通过设置epoll_event->events=EPOLLET;实现边沿触发。
- 通常来说,ET方式是比较危险的方式,如果要使用ET方式,那么,应用程序应该 - 1、将socket设置为non-blocking方式; 2、epoll_wait收到event后,read或write需要读到没有数据为止,write需要写到没有数据为止(对于non-blocking socket来说,EAGAIN通常是无数据可读,无数据可写的返回状态)。
void callback(int fd, void *data)
{
char *buf[BUFSIZ];
int n;
n=read(fd, buf, BUFSIZ);
if(n<0) return -1;//error
if(n==0) return 0;//EOF
dosomething(fd, buf, n);
retrun 1;//success
}
//non-blocking+电平触发,即可完成数据读取
void callback(int fd, void *data)
{
char *buf[BUFSIZ];
int n;
for(;;){
n=read(fd, buf, BUFSIZ);
if(n<0) return -1;//error
if(n==0) return 0;//EOF
dosomething(fd, buf, n);
if(n<BUFSIZ) break;
}
retrun 1;//success
}
//函数只能处理最后一组数据<BUFSIZ的情况,当=BUFSIZ时,程序阻塞在read。
- read(2)+seclect:检查缓冲区是否还有数据
#include <sys/time.h>
#include <sys/types.h>
int checking_read(int fd, char *buf, int len, int *count)
{
int n;
*count = 0;
n = read(fd, buf, len);
if (n > 0) {
fd_set fds;
struct timeval tv;
int c;
FD_ZERO(&fds);
FD_SET(fd, &fds);
tv.tv_sec = 0;
tv.tv_usec = 0; //不会阻塞
c = select(fd + 1, &fds, NULL, NULL, &tv);
if (c == 1) {
*count = 1;
}
}
return n;
}
void callback(int fd, void *data)
{
char *buf[BUFSIZ];
int n, count;
for (;;) {
n = checking_read(fd, buf, BUFSIZ, &count);
if (n < 0) return -1; //error
if (n == 0) return 0; //EOF
dosomething(fd, buf, n);
if (!count) break;
}
retrun 1; //success
}
- read+O_NONBLOCK:当输入输出发生阻塞时,系统调用产生“继续执行的话会发生阻塞”的错误——EAGAIN
#include <fcntl.h>
#include <errno.h>
void init(int fd)
{
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, count;
for (;;) {
n = read(fd, buf, BUFSIZ, &count);
if (n < 0) {
if (errno == EAGAIN) //缓冲区为空
return 1;
else
return -1; //error
}
if (n == 0) return 0; //EOF
dosomething(fd, buf, n);
}
retrun 1; //success
}
- aio_read:异步I/O,aio_read, aio_write, aio_fsync, aio_error, aio_return, aio_cancel, aio_suspend
//异步I/O时,使用aio_suspend/aio_error检查请求是否完成
#include <aio.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
int main()
{
struct aiocb cb;
const struct aiocb *cblist[1];
char buf[BUFSIZ];
int fd, n;
fd = open("/tmp/a.c", O_RDONLY);
memset(&cb, 0, sizeof(cb));
cb.aio_fildes = fd;
cb.aio_buf = buf;
cb.aio_nbytes = BUFSIZ;
n = aio_read(&cb); //aio_read:提交请求,预约read系统调用,并立即返回
if (n < 0) perror("aio_read");
#if AIO_SUSPEND //aio_suspend:请求等待
cblist[0] = &cb;
n = aio_suspend(cblist, 1, NULL);
if (n < 0) perror("aio_suspend");
#else AIO_ERROR //aio_error:获取错误状态,aio未完成时返回EINPROGRESS
while (aio_error(&cb) == EINPROGRESS)
printf("retry\n");
#endif
n = aio_return(&cb); //获取返回值
if (n < 0) perror("aio_return");
printf("%d %d ---\n%.*s", n, cb.aio_nbytes, cb.aio_buf);
return 0;
}
//异步I/O时,指定回调函数,I/O结束时调用(调用方式:线程/信号)
#include <aio.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <errno.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) perror("aio_return");
printf("%d %d ---\n%.*s", n, cb->aio_nbytes, cb->aio_buf);
exit(0);
}
return ;
}
int main()
{
struct aiocb cb;
char buf[BUFSIZ];
int fd, n;
fd = open("/tmp/a.c", O_RDONLY);
memset(&cb, 0, sizeof(cb));
cb.aio_fildes = fd;
cb.aio_buf = buf;
cb.aio_nbytes = BUFSIZ;
cb.aio_sigevent.sigev_notify = SIGEV_THREAD;//用多线程实现异步I/O,回调函数是在独立于主线程的另一个独立线程中完成
cb.aio_sigevent.sigev_notify_function = read_done;//指定回调函数
cb.aio_sigevent.sigev_value.sival_ptr = &cb;
n = aio_read(&cb); //aio_read:提交请求,预约read系统调用,并立即返回
if (n < 0) perror("aio_read");
select(0, NULL, NULL, NULL, NULL);
return 0;
}
笔者按
【笔者按】又是一篇誊写目录的笔记,刚好对应松本行弘系列书的上下篇。拖延症真的是病——本来书的阅读已经完成,剩下的“笔者按”一拖,就拖到第二年;得治!有一些朋友可能会奇怪,你一个做嵌入式的,为什么还看这些关于“面向对象”甚至是“脚本”的书,不得不说松本行弘确实是一个牛逼的程序员我要聆听大师的教诲……屁啊还不是因为在“与世界分享你的知识、经验和见解”的时候看到嵌入式脚本语言——Ruby,然后看完书了才发觉不对劲人家嵌入的是网页中你的嵌入是硬件中资源丝毫必争差个十万八千里。但我还是想说说我的见解,松本行弘说到这个行业的发展——多核并行,说到一门语言设计时的思考及发展——优缺点及逐级抽象;我们不妨相像一下嵌入式硬件资源未来的发展,想像一下除了C未来会不会有更合适的嵌入式语言;甚至最基本的,完成一个软件项目需要思考什么。这阵子有点跑偏了,以后应该会更回归Linux一点。