前言
以<深入理解计算机系统>(以下称“本书”)内容为基础,对程序的整个过程进行梳理。本书内容对整个计算机系统做了系统性导引,每部分内容都是单独的一门课.学习深度根据自己需要来定
引入
接续理解计算机系统_并发编程(8)_线程(五):生产者-消费者问题-优快云博客,这篇帖子看读者-写者问题.
线程的分析
线程的"不确定性"
目前对线程的理解有了一些基础,再想想这部分内容给人一种"不确定"的感觉.编程是逻辑的科学,逻辑代表"绝对理性"或者"必然性".学习也在追求一种"必然",学会的东西照着这种"必然"应用在同样的情境下.分析下线程的"不确定性"来源:
1>线程代码是"乱序"的.由于CPU的中断机制,在访问和修改共享变量时必须考虑同步.虽然通过信号量加锁可以解决这个问题.但读代码的时候必须时刻考虑:某条语句执行完以后从前面某个位置开始..
2>线程由内核调度,无法追踪.
假设程序员写了n个线程,要求其中第1个线程具有优先级,例如每次经过P(v)操作都排在第一,因为由内核调度,所以无法实现(如果修改内核可以当笔者没说,但目前掌握的知识是无法实现的).
=============================内容分割线↓===================================
以第2条延伸思考,如何让某个线程具有优先权?他可以通过线程函数thread实现
免责声明:以下内容纯属虚构(如有雷同实属巧合)
假如有一个想在网上抢票的黄牛(scalper),他该怎样做?
服务器端的程序员在写thread函数的同时,加入一些内容,当检测到其地址时,给与一些"便利".
---以上是异想天开内容
=============================内容分割线↑===================================
小结:线程的产生是不可控的,线程的内容是可控的.
线程的抽象
逻辑代表理性.在理解和记忆某个概念时,建立一种"感性"的抽象.尝试建立起对线程的抽象
1>共享数据.为了解决同步问题,引入了信号量做互斥锁
2>共享函数.
3>不可知的执行顺序
=============================内容分割线↓===================================
引申:把1和2综合起来,一个线程每次修改数据,调用相同的函数.是不是很像一个for循环?即一个单任务可以化成一个多线程任务.这样做可能是"画蛇添足",也可能十分有用.
以"有用"做前提,分析他的优点来自于:用计算机(线程)并发提高工作效率.
再想一想适用于什么样的场景,笔者想了两个
1>大型单机应用程序的数据读写,比如某个游戏的图像渲染(本书虽然没有提到多核CPU对线程的支持,但那不是写应用程序员该关心的事).单机任务分解成多线程任务
2>对于服务器而言,这样做能不能提高效率? 这个话题很大---如何提高服务器效率?
笔者感觉问题的核心在于:每个线程函数(thread函数)的代码量多少才合适?
简单考虑影响线程代码量多少的因素:服务器CPU的计算能力,用户体验等等.需要写代码验证
他甚至可以作为程序员职业的主力方向去努力.
=============================内容分割线↑===================================
基于线程抽象产生的程序模型
笔者把几种程序模型,归纳为基于线程的抽象(个人专利(*^_^*),转载请注明出处)
1.任务线程化模型.
变化的共享数据,加共享函数,使单任务多线程解决,提高工作效率.
2.生产者-消费者模型
共享函数返回值产生数据集合,形成缓冲区.自动调节生产者和消费者.
3.读者-写者模型
共享数据可被多个线程同时访问,但修改时必须只能有一个线程.
=============================内容分割线↓===================================
/*能思考出程序模型并写代码实现的,被视为行业先驱,多积累多思考,说不定某天自己写出来一个*/
此外,这几种已实现的程序模型,能不能"组合"起来,应对某种场景?留意
=============================内容分割线↑===================================
读者-写者模式应用场景
本书P707第2段:读者-写者交互在现实系统中很常见.例如,一个在线航空预定系统中,允许有无限多个客户同时查看座位分配,但是正在预定座位的客户必须拥有对数据库的独占的访问.
再看另一个例子,在一个多线程缓存Web代理中,无限多个线程可以从共享页面缓存中取出已有的页面,但是任何向缓存中写入一个新页面的线程必须拥有独占的访问. ---黑体字是原话
代码解读
本书图12-26举了一个"读者优先"的例子
首先,从非线程函数到线程函数,才开始非常不习惯(目前笔者尚在适应).线程函数是非常难读写的,原因是前面讲过:线程代码是乱序的.
int readcnt; //读操作计数,初始化为0;
sem_t mutex,w; //两个锁--读锁和写锁,初始化为1
void reader(void) //读操作
{
while(1){
P(&mutex); //第7行
readcnt++; //第8行
if(readcnt==1) //第9行
P(&w); //第10行
V(&mutex); //第11行
/* 读发生 */
P(&mutex); //第14行:第2个P操作
readcnt--;
if(readcnt==0)
V(&w); //第17行
V(&mutex);
}
void writer(void) //写操作
{
while(1){
P(&w);
/* 写发生 */
V(&w);
}
}
整体是这个意思(需求):允许有n个线程进行读操作,但只能有1个线程进行写操作.分两种情况:
1.如果当前线程进行读操作,无法进行写操作,因为第10行写锁发挥作用.必须等所有读线程完成后,写锁才被解开(第17行).下一个进来的线程可以进行写操作.这正是体现了"读者优先"
2.如果当前线程在进行写操作,不影响其他线程进行读操作.
其他:
1.从第7行到第11行,用readcnt控制写锁.第11行解开读锁,意思是从读操作来看,7~11行可以省略,任意读.所以,第7到11行其实就干了一件事:掌握写锁.从第14行开始,如果达到条件:所有读的线程执行完毕,才解开写锁.
2.注意:代码无法应用到生产环境,需要增加内容,没有共享数据(readcnt不算,他用来控制写锁).具体不展开
3.如何实现 "写者优先"?很简单,把两个函数名调换一下.全局变量中把readcnt改成writecnt
小结
读者-写者模式简单解读
线程有很多地方值得思考,代码读写要困难一些.
并发是程序设计的重点,也是难点,需要多多研究.