前言
以<深入理解计算机系统>(以下称“本书”)内容为基础,对程序的整个过程进行梳理。本书内容对整个计算机系统做了系统性导引,每部分内容都是单独的一门课.学习深度根据自己需要来定
引入
谈论学习中遇到的问题和应对,以及并发编程中的I/O复用.
基于I/O复用的并发,在学习过程中产生了很久没见的"痛苦"感觉.思考原因及解决办法.
程序员学习的迷茫时刻
笔者认为有两个阶段的学习,是比较"难受"的.一是对语言语法的学习;二是框架的学习.
语法学习
才开始进入编程学习时,一般都是以一门语言入手.比如笔者先学C,再看了一段时间Java,后来把主要方向集中在了C++.语法学习的目的,简单说一是让程序通过编译,不要让程序有拼写错误;二是语句表达了什么意思.
语法从根本上来说是对数据的处理(对C/C++而言,Java在底层封装了数据处理,表面不明显).说到数据,马上进入了数据结构,所以语法重点在于数据结构和算法.
进入面向对象之后,又到了接口和数据类型的设计.---后面还有设计模式(设计模式是对类型设计方法的总结,会面对具体应用场景,笔者在这方面并没有很深入)
语法学习,多看(别人写的代码)多用(自己写代码)慢慢熟悉.在编程的学习中,语法是基础,也是底气.但如果不想成为语言方面的专家,不要把太多精力放在语法上了.比如C++的语法随着版本迭代越整越复杂,笔者个人感觉模板是比较重要的,其他的内容到用的时候再去查阅.语法的核心是对数据的表达和操作.
语法最大的好处是可以复用,新版本一般兼容老版本,即使学习成本大也值.
框架学习
首先框架是必须的,他的定义是半成品软件.如果不用别人写的框架就用自己写的框架.框架也可以迭代,在现有框架基础上开发新的框架.所以他是好东西.
当想构建一个软件,需要用框架的时候,学习遇到瓶颈的原因是这样的:
框架的模型,相关的概念,配套api都是别人提供的,直到问题被解决,使用框架的人像一个"局外人"完全没参与感.而且在使用框架api的时候,所学的语法可能完全用不上,所以容易产生一种"挫败"的感觉.
解决方法有两个:
1>耐住性子,像学习语法一样学习框架,最好是研习源码;但这个方法有一点问题,如果是应用级的框架,能看懂没问题.如果框架是基于内核的,可能需要投入的精力更多.
2>对照现成代码,把函数意思弄明白.用的时候复制代码修改相关参数,也就是"复制粘贴".不要感觉"羞耻",学习的目的也是为了使用,而这种方法是"捷径".---如果能行学习成本低,如果代码出问题,那么成本也不低.
还可以把两种方法结合起来:尽量读懂,读不懂的部分再"复制粘贴"
基于I/O复用的并发:select函数
问题和思路
本书P684提了一个问题:服务器必须响应两个互相独立的I/O事件时,该如何应对?
解决方法:针对这种困境的一个解决方法就是I/O多路复用技术.基本的思路就是使用select函数,要求内核挂起进程,只有在一个或多个I/O事件发生后,才将控制返回给应用程序. ---黑体字是原话
select函数
首先这个api的说明也是让人头大,主要是本书没有源码,强行记忆又会陷入事半功倍的境地,所以如前所述,尽量读懂,读不懂就"抄".
select函数原型如下:
//select函数原型
int select(int n,fd_set *fdset,NULL,NULL,NULL);
//返回已准备好的描述符的非零的个数,若出错则为-1
代码解读
结合本书P685中的说明和P686的select.c,看select函数的使用.以下整段黑体字是本书原话:
select函数处理类型为fd_set的集合,也叫做描述符集合.逻辑上,我们将描述符集合看成一个大小为n的位向量.当且仅当bk=1,描述符k才表明是描述符集合的一个元素.
---解读:位向量的概念在上一篇帖子理解计算机系统_并发编程_几个概念---网络IO模型&位向量-CSDN博客有说明,这里注意的是fd_set是什么类型,理解成是int,long或者char,short都可以,后面的fd_set*表示对应类型的数组,这样理解没问题.
针对我们的目的,select函数有两个输入:一个称为读集合的描述符集合(fdset)和该读集合的基数(n).select函数会一直阻塞,直到读集合中至少有一个描述符准备好可以读.当且仅当一个从该描述符读取一个字节的请求不会阻塞时,描述符就表示准备好可以读了.
---解读:描述符集合的基数n,在P686代码第24行,传入的是listenfd+1,就是4.表示该读集合的基数4,进程默认3个文件描述符0,1和2.代码第16行listenfd是生成的监听描述符,进程中为3,所以共4个描述符,所以传入4.
select函数会阻塞(睡眠或等待)直到一个描述符准备好,程序得以继续进行---执行代码第25行
select有一个副作用,他修改参数fdset指向的fd_set,知名读集合的一个子集,称为准备好集合,这个集合是由读集合中准备好可以读了的描述符组成的.该函数的返回值指明了准备好集合的基数.注意,由于这个副作用,我们必须在每次调用select时都更新读集合.
---解读:注意后面的宏函数FD_ISSET操作的是准备好集合&ready_set.原始集合&read_set是一个范围,而准备好集合&ready_set是结果,从结果中去筛选并解决问题.
select函数返回值没看见有什么作用.
细节
select.c并不能处理并发,原因是代码第31行调用了Close(connfd)来关闭文件描述符connfd.如果不关闭,他是进程中第4个描述符.如果再有客户端连接进来---从第22行开始,那么第24行传入的n应等于5,Select函数可能会出错.因此select.c只是演示select函数的用法,没有实用价值.
修正
此前笔者一直以为listenfd是客户端准备阶段所生成的描述符.由此例可推断这种认识是有问题的.监听描述符listenfd是客户端调用connect后产生的,应归于连接阶段.服务器在此基础上调用accept建立和客户端的连接.
小结
select函数的一点理解.